@wecom/wecom-openclaw-plugin 2026.3.31-beta.0 → 2026.4.1-beta.1

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 (209) hide show
  1. package/README.md +4 -251
  2. package/dist/cjs/index.js +45 -0
  3. package/dist/cjs/index.js.map +1 -0
  4. package/dist/cjs/src/channel.js +339 -0
  5. package/dist/cjs/src/channel.js.map +1 -0
  6. package/dist/cjs/src/const.js +137 -0
  7. package/dist/cjs/src/const.js.map +1 -0
  8. package/dist/cjs/src/dm-policy.js +89 -0
  9. package/dist/cjs/src/dm-policy.js.map +1 -0
  10. package/dist/cjs/src/group-policy.js +133 -0
  11. package/dist/cjs/src/group-policy.js.map +1 -0
  12. package/dist/cjs/src/mcp/interceptors/biz-error.js +70 -0
  13. package/dist/cjs/src/mcp/interceptors/biz-error.js.map +1 -0
  14. package/dist/cjs/src/mcp/interceptors/index.js +73 -0
  15. package/dist/cjs/src/mcp/interceptors/index.js.map +1 -0
  16. package/dist/cjs/src/mcp/interceptors/msg-media.js +159 -0
  17. package/dist/cjs/src/mcp/interceptors/msg-media.js.map +1 -0
  18. package/dist/cjs/src/mcp/interceptors/smartpage-create.js +157 -0
  19. package/dist/cjs/src/mcp/interceptors/smartpage-create.js.map +1 -0
  20. package/dist/cjs/src/mcp/interceptors/smartpage-export.js +93 -0
  21. package/dist/cjs/src/mcp/interceptors/smartpage-export.js.map +1 -0
  22. package/dist/cjs/src/mcp/schema.js +104 -0
  23. package/dist/cjs/src/mcp/schema.js.map +1 -0
  24. package/dist/cjs/src/mcp/tool.js +208 -0
  25. package/dist/cjs/src/mcp/tool.js.map +1 -0
  26. package/dist/cjs/src/mcp/transport.js +417 -0
  27. package/dist/cjs/src/mcp/transport.js.map +1 -0
  28. package/dist/cjs/src/media-handler.js +129 -0
  29. package/dist/cjs/src/media-handler.js.map +1 -0
  30. package/dist/cjs/src/media-uploader.js +284 -0
  31. package/dist/cjs/src/media-uploader.js.map +1 -0
  32. package/dist/cjs/src/message-parser.js +134 -0
  33. package/dist/cjs/src/message-parser.js.map +1 -0
  34. package/dist/cjs/src/message-sender.js +89 -0
  35. package/dist/cjs/src/message-sender.js.map +1 -0
  36. package/dist/cjs/src/monitor.js +991 -0
  37. package/dist/cjs/src/monitor.js.map +1 -0
  38. package/dist/cjs/src/onboarding.js +182 -0
  39. package/dist/cjs/src/onboarding.js.map +1 -0
  40. package/dist/cjs/src/openclaw-compat.js +400 -0
  41. package/dist/cjs/src/openclaw-compat.js.map +1 -0
  42. package/dist/cjs/src/reqid-store.js +88 -0
  43. package/dist/cjs/src/reqid-store.js.map +1 -0
  44. package/dist/cjs/src/runtime.js +16 -0
  45. package/dist/cjs/src/runtime.js.map +1 -0
  46. package/dist/cjs/src/state-dir-resolve.js +35 -0
  47. package/dist/cjs/src/state-dir-resolve.js.map +1 -0
  48. package/dist/cjs/src/state-manager.js +158 -0
  49. package/dist/cjs/src/state-manager.js.map +1 -0
  50. package/dist/cjs/src/template-card-parser.js +641 -0
  51. package/dist/cjs/src/template-card-parser.js.map +1 -0
  52. package/dist/cjs/src/timeout.js +42 -0
  53. package/dist/cjs/src/timeout.js.map +1 -0
  54. package/dist/cjs/src/utils.js +60 -0
  55. package/dist/cjs/src/utils.js.map +1 -0
  56. package/dist/cjs/src/version.js +26 -0
  57. package/dist/cjs/src/version.js.map +1 -0
  58. package/dist/esm/index.js +41 -0
  59. package/dist/esm/index.js.map +1 -0
  60. package/dist/esm/src/channel.js +337 -0
  61. package/dist/esm/src/channel.js.map +1 -0
  62. package/dist/{src → esm/src}/const.js +30 -77
  63. package/dist/esm/src/const.js.map +1 -0
  64. package/dist/{src → esm/src}/dm-policy.js +9 -6
  65. package/dist/esm/src/dm-policy.js.map +1 -0
  66. package/dist/{src → esm/src}/group-policy.js +7 -3
  67. package/dist/esm/src/group-policy.js.map +1 -0
  68. package/dist/{src → esm/src}/mcp/interceptors/biz-error.js +6 -2
  69. package/dist/esm/src/mcp/interceptors/biz-error.js.map +1 -0
  70. package/dist/{src → esm/src}/mcp/interceptors/index.js +10 -6
  71. package/dist/esm/src/mcp/interceptors/index.js.map +1 -0
  72. package/dist/{src → esm/src}/mcp/interceptors/msg-media.js +10 -6
  73. package/dist/esm/src/mcp/interceptors/msg-media.js.map +1 -0
  74. package/dist/{src → esm/src}/mcp/interceptors/smartpage-create.js +6 -2
  75. package/dist/esm/src/mcp/interceptors/smartpage-create.js.map +1 -0
  76. package/dist/{src → esm/src}/mcp/interceptors/smartpage-export.js +6 -2
  77. package/dist/esm/src/mcp/interceptors/smartpage-export.js.map +1 -0
  78. package/dist/{src → esm/src}/mcp/schema.js +4 -1
  79. package/dist/esm/src/mcp/schema.js.map +1 -0
  80. package/dist/{src → esm/src}/mcp/tool.js +8 -4
  81. package/dist/esm/src/mcp/tool.js.map +1 -0
  82. package/dist/{src → esm/src}/mcp/transport.js +16 -15
  83. package/dist/esm/src/mcp/transport.js.map +1 -0
  84. package/dist/{src → esm/src}/media-handler.js +10 -6
  85. package/dist/esm/src/media-handler.js.map +1 -0
  86. package/dist/{src → esm/src}/media-uploader.js +10 -64
  87. package/dist/esm/src/media-uploader.js.map +1 -0
  88. package/dist/{src → esm/src}/message-parser.js +4 -1
  89. package/dist/esm/src/message-parser.js.map +1 -0
  90. package/dist/{src → esm/src}/message-sender.js +11 -46
  91. package/dist/esm/src/message-sender.js.map +1 -0
  92. package/dist/{src → esm/src}/monitor.js +282 -151
  93. package/dist/esm/src/monitor.js.map +1 -0
  94. package/dist/{src → esm/src}/onboarding.js +26 -22
  95. package/dist/esm/src/onboarding.js.map +1 -0
  96. package/dist/{src → esm/src}/openclaw-compat.js +27 -143
  97. package/dist/esm/src/openclaw-compat.js.map +1 -0
  98. package/dist/{src → esm/src}/reqid-store.js +7 -4
  99. package/dist/esm/src/reqid-store.js.map +1 -0
  100. package/dist/{src → esm/src}/runtime.js +5 -2
  101. package/dist/esm/src/runtime.js.map +1 -0
  102. package/dist/esm/src/state-dir-resolve.js +13 -0
  103. package/dist/esm/src/state-dir-resolve.js.map +1 -0
  104. package/dist/{src → esm/src}/state-manager.js +16 -84
  105. package/dist/esm/src/state-manager.js.map +1 -0
  106. package/dist/{src → esm/src}/template-card-parser.js +8 -17
  107. package/dist/esm/src/template-card-parser.js.map +1 -0
  108. package/dist/{src → esm/src}/timeout.js +5 -2
  109. package/dist/esm/src/timeout.js.map +1 -0
  110. package/dist/{src → esm/src}/utils.js +10 -24
  111. package/dist/esm/src/utils.js.map +1 -0
  112. package/dist/esm/src/version.js +23 -0
  113. package/dist/esm/src/version.js.map +1 -0
  114. package/dist/esm/types/index.d.ts +9 -0
  115. package/dist/{src → esm/types/src}/const.d.ts +0 -46
  116. package/dist/{src → esm/types/src}/interface.d.ts +2 -0
  117. package/dist/{src → esm/types/src}/message-sender.d.ts +0 -18
  118. package/dist/{src → esm/types/src}/openclaw-compat.d.ts +0 -49
  119. package/dist/esm/types/src/state-dir-resolve.d.ts +2 -0
  120. package/dist/{src → esm/types/src}/utils.d.ts +0 -39
  121. package/dist/esm/types/src/version.d.ts +2 -0
  122. package/dist/index.d.ts +4 -2
  123. package/package.json +7 -15
  124. package/dist/index.js +0 -113
  125. package/dist/src/accounts.d.ts +0 -57
  126. package/dist/src/accounts.js +0 -222
  127. package/dist/src/agent/api-client.d.ts +0 -94
  128. package/dist/src/agent/api-client.js +0 -292
  129. package/dist/src/agent/handler.d.ts +0 -63
  130. package/dist/src/agent/handler.js +0 -532
  131. package/dist/src/agent/index.d.ts +0 -5
  132. package/dist/src/agent/index.js +0 -5
  133. package/dist/src/agent/webhook.d.ts +0 -23
  134. package/dist/src/agent/webhook.js +0 -226
  135. package/dist/src/agent/xml.d.ts +0 -21
  136. package/dist/src/agent/xml.js +0 -41
  137. package/dist/src/channel.js +0 -564
  138. package/dist/src/chat-queue.d.ts +0 -31
  139. package/dist/src/chat-queue.js +0 -45
  140. package/dist/src/http.d.ts +0 -26
  141. package/dist/src/http.js +0 -93
  142. package/dist/src/interface.js +0 -4
  143. package/dist/src/mcp/index.js +0 -6
  144. package/dist/src/mcp/interceptors/types.js +0 -7
  145. package/dist/src/shared/command-auth.d.ts +0 -23
  146. package/dist/src/shared/command-auth.js +0 -73
  147. package/dist/src/shared/xml-parser.d.ts +0 -46
  148. package/dist/src/shared/xml-parser.js +0 -210
  149. package/dist/src/target.d.ts +0 -35
  150. package/dist/src/target.js +0 -67
  151. package/dist/src/template-card-manager.d.ts +0 -55
  152. package/dist/src/template-card-manager.js +0 -205
  153. package/dist/src/types/account.d.ts +0 -29
  154. package/dist/src/types/account.js +0 -4
  155. package/dist/src/types/config.d.ts +0 -80
  156. package/dist/src/types/config.js +0 -7
  157. package/dist/src/types/constants.d.ts +0 -42
  158. package/dist/src/types/constants.js +0 -42
  159. package/dist/src/types/index.d.ts +0 -7
  160. package/dist/src/types/index.js +0 -5
  161. package/dist/src/types/message.d.ts +0 -238
  162. package/dist/src/types/message.js +0 -5
  163. package/dist/src/version.d.ts +0 -2
  164. package/dist/src/version.js +0 -24
  165. package/dist/src/webhook/command-auth.d.ts +0 -47
  166. package/dist/src/webhook/command-auth.js +0 -96
  167. package/dist/src/webhook/gateway.d.ts +0 -36
  168. package/dist/src/webhook/gateway.js +0 -227
  169. package/dist/src/webhook/handler.d.ts +0 -18
  170. package/dist/src/webhook/handler.js +0 -420
  171. package/dist/src/webhook/helpers.d.ts +0 -156
  172. package/dist/src/webhook/helpers.js +0 -833
  173. package/dist/src/webhook/http.d.ts +0 -26
  174. package/dist/src/webhook/http.js +0 -94
  175. package/dist/src/webhook/index.d.ts +0 -11
  176. package/dist/src/webhook/index.js +0 -14
  177. package/dist/src/webhook/media.d.ts +0 -29
  178. package/dist/src/webhook/media.js +0 -88
  179. package/dist/src/webhook/monitor.d.ts +0 -59
  180. package/dist/src/webhook/monitor.js +0 -1147
  181. package/dist/src/webhook/state.d.ts +0 -220
  182. package/dist/src/webhook/state.js +0 -491
  183. package/dist/src/webhook/target.d.ts +0 -41
  184. package/dist/src/webhook/target.js +0 -132
  185. package/dist/src/webhook/types.d.ts +0 -347
  186. package/dist/src/webhook/types.js +0 -33
  187. /package/dist/{src → esm/types/src}/channel.d.ts +0 -0
  188. /package/dist/{src → esm/types/src}/dm-policy.d.ts +0 -0
  189. /package/dist/{src → esm/types/src}/group-policy.d.ts +0 -0
  190. /package/dist/{src → esm/types/src}/mcp/index.d.ts +0 -0
  191. /package/dist/{src → esm/types/src}/mcp/interceptors/biz-error.d.ts +0 -0
  192. /package/dist/{src → esm/types/src}/mcp/interceptors/index.d.ts +0 -0
  193. /package/dist/{src → esm/types/src}/mcp/interceptors/msg-media.d.ts +0 -0
  194. /package/dist/{src → esm/types/src}/mcp/interceptors/smartpage-create.d.ts +0 -0
  195. /package/dist/{src → esm/types/src}/mcp/interceptors/smartpage-export.d.ts +0 -0
  196. /package/dist/{src → esm/types/src}/mcp/interceptors/types.d.ts +0 -0
  197. /package/dist/{src → esm/types/src}/mcp/schema.d.ts +0 -0
  198. /package/dist/{src → esm/types/src}/mcp/tool.d.ts +0 -0
  199. /package/dist/{src → esm/types/src}/mcp/transport.d.ts +0 -0
  200. /package/dist/{src → esm/types/src}/media-handler.d.ts +0 -0
  201. /package/dist/{src → esm/types/src}/media-uploader.d.ts +0 -0
  202. /package/dist/{src → esm/types/src}/message-parser.d.ts +0 -0
  203. /package/dist/{src → esm/types/src}/monitor.d.ts +0 -0
  204. /package/dist/{src → esm/types/src}/onboarding.d.ts +0 -0
  205. /package/dist/{src → esm/types/src}/reqid-store.d.ts +0 -0
  206. /package/dist/{src → esm/types/src}/runtime.d.ts +0 -0
  207. /package/dist/{src → esm/types/src}/state-manager.d.ts +0 -0
  208. /package/dist/{src → esm/types/src}/template-card-parser.d.ts +0 -0
  209. /package/dist/{src → esm/types/src}/timeout.d.ts +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openclaw-compat.js","sources":["../../../../../src/openclaw-compat.ts"],"sourcesContent":["/**\n * openclaw plugin-sdk 高版本方法兼容层\n *\n * 部分方法(如 loadOutboundMediaFromUrl、detectMime、getDefaultMediaLocalRoots)\n * 仅在较新版本的 openclaw plugin-sdk 中才导出。\n *\n * 本模块在加载时一次性探测 SDK 导出,存在则直接 re-export SDK 版本,\n * 不存在则导出 fallback 实现。其他模块统一从本文件导入,无需关心底层兼容细节。\n */\n\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\nimport { resolveStateDir } from \"./state-dir-resolve\";\n\nexport const DEFAULT_ACCOUNT_ID = \"default\";\n\n// ============================================================================\n// 类型定义\n// ============================================================================\n\n/** 与 openclaw plugin-sdk 中 WebMediaResult 兼容的类型 */\nexport type WebMediaResult = {\n buffer: Buffer;\n contentType?: string;\n kind?: string;\n fileName?: string;\n};\n\nexport type OutboundMediaLoadOptions = {\n maxBytes?: number;\n mediaLocalRoots?: readonly string[];\n};\n\nexport type DetectMimeOptions = {\n buffer?: Buffer;\n headerMime?: string | null;\n filePath?: string;\n};\n\n// ============================================================================\n// SDK 一次性探测(模块加载时执行,结果缓存)\n// ============================================================================\n\ninterface SdkExports {\n loadOutboundMediaFromUrl?: (url: string, opts?: OutboundMediaLoadOptions) => Promise<WebMediaResult>;\n detectMime?: (opts: DetectMimeOptions) => Promise<string | undefined>;\n getDefaultMediaLocalRoots?: () => readonly string[];\n addWildcardAllowFrom?: (allowFrom: string[]) => string[];\n}\n\nconst _sdkReady: Promise<SdkExports> = import(\"openclaw/plugin-sdk/core\")\n .then((sdk: any) => {\n const exports: SdkExports = {};\n if (typeof sdk.loadOutboundMediaFromUrl === \"function\") {\n exports.loadOutboundMediaFromUrl = sdk.loadOutboundMediaFromUrl;\n }\n if (typeof sdk.detectMime === \"function\") {\n exports.detectMime = sdk.detectMime;\n }\n if (typeof sdk.getDefaultMediaLocalRoots === \"function\") {\n exports.getDefaultMediaLocalRoots = sdk.getDefaultMediaLocalRoots;\n }\n if (typeof sdk.addWildcardAllowFrom === \"function\") {\n exports.addWildcardAllowFrom = sdk.addWildcardAllowFrom;\n }\n return exports;\n })\n .catch(() => {\n // openclaw/plugin-sdk 不可用或版本过低,全部使用 fallback\n return {} as SdkExports;\n });\n\n// 同时尝试从 plugin-sdk/setup 模块探测(缓存同步可用的引用)\nlet _cachedAddWildcardAllowFrom: ((allowFrom: string[]) => string[]) | undefined;\n\nconst _setupSdkReady: Promise<void> = import(\"openclaw/plugin-sdk/setup\")\n .then((sdk: any) => {\n if (typeof sdk.addWildcardAllowFrom === \"function\") {\n _cachedAddWildcardAllowFrom = sdk.addWildcardAllowFrom;\n }\n })\n .catch(() => { /* plugin-sdk/setup 不可用 */ });\n\n// 同时也从 core 模块的结果中缓存\n_sdkReady.then((sdk) => {\n if (!_cachedAddWildcardAllowFrom && sdk.addWildcardAllowFrom) {\n _cachedAddWildcardAllowFrom = sdk.addWildcardAllowFrom;\n }\n});\n\n// ============================================================================\n// detectMime —— 检测 MIME 类型\n// ============================================================================\n\nconst MIME_BY_EXT: Record<string, string> = {\n \".heic\": \"image/heic\",\n \".heif\": \"image/heif\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".webp\": \"image/webp\",\n \".gif\": \"image/gif\",\n \".ogg\": \"audio/ogg\",\n \".mp3\": \"audio/mpeg\",\n \".m4a\": \"audio/x-m4a\",\n \".mp4\": \"video/mp4\",\n \".mov\": \"video/quicktime\",\n \".pdf\": \"application/pdf\",\n \".json\": \"application/json\",\n \".zip\": \"application/zip\",\n \".gz\": \"application/gzip\",\n \".tar\": \"application/x-tar\",\n \".7z\": \"application/x-7z-compressed\",\n \".rar\": \"application/vnd.rar\",\n \".doc\": \"application/msword\",\n \".xls\": \"application/vnd.ms-excel\",\n \".ppt\": \"application/vnd.ms-powerpoint\",\n \".docx\": \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n \".xlsx\": \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n \".pptx\": \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n \".csv\": \"text/csv\",\n \".txt\": \"text/plain\",\n \".md\": \"text/markdown\",\n \".amr\": \"audio/amr\",\n \".aac\": \"audio/aac\",\n \".wav\": \"audio/wav\",\n \".webm\": \"video/webm\",\n \".avi\": \"video/x-msvideo\",\n \".bmp\": \"image/bmp\",\n \".svg\": \"image/svg+xml\",\n};\n\n/** 通过 buffer 魔术字节嗅探 MIME 类型(动态导入 file-type,不强依赖) */\nasync function sniffMimeFromBuffer(buffer: Buffer): Promise<string | undefined> {\n try {\n const { fileTypeFromBuffer } = await import(\"file-type\");\n const type = await fileTypeFromBuffer(buffer);\n return type?.mime ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/** fallback 版 detectMime,参考 weclaw/src/media/mime.ts */\nasync function detectMimeFallback(opts: DetectMimeOptions): Promise<string | undefined> {\n const ext = opts.filePath ? path.extname(opts.filePath).toLowerCase() : undefined;\n const extMime = ext ? MIME_BY_EXT[ext] : undefined;\n\n const sniffed = opts.buffer ? await sniffMimeFromBuffer(opts.buffer) : undefined;\n\n const isGeneric = (m?: string) =>\n !m || m === \"application/octet-stream\" || m === \"application/zip\";\n\n if (sniffed && (!isGeneric(sniffed) || !extMime)) {\n return sniffed;\n }\n if (extMime) {\n return extMime;\n }\n const headerMime = opts.headerMime?.split(\";\")?.[0]?.trim().toLowerCase();\n if (headerMime && !isGeneric(headerMime)) {\n return headerMime;\n }\n if (sniffed) {\n return sniffed;\n }\n if (headerMime) {\n return headerMime;\n }\n return undefined;\n}\n\n/**\n * 检测 MIME 类型(兼容入口)\n *\n * 支持两种调用签名以兼容不同使用场景:\n * - detectMime(buffer) → 旧式调用\n * - detectMime({ buffer, headerMime, filePath }) → 完整参数\n *\n * 优先使用 SDK 版本,不可用时使用 fallback。\n */\nexport async function detectMime(\n bufferOrOpts: Buffer | DetectMimeOptions,\n): Promise<string | undefined> {\n const sdk = await _sdkReady;\n\n const opts: DetectMimeOptions = Buffer.isBuffer(bufferOrOpts)\n ? { buffer: bufferOrOpts }\n : bufferOrOpts;\n\n if (sdk.detectMime) {\n try {\n return await sdk.detectMime(opts);\n } catch {\n // SDK detectMime 异常,降级到 fallback\n }\n }\n return detectMimeFallback(opts);\n}\n\n// ============================================================================\n// loadOutboundMediaFromUrl —— 从 URL/路径加载媒体文件\n// ============================================================================\n\n/** 安全的本地文件路径校验,参考 weclaw/src/web/media.ts */\nasync function assertLocalMediaAllowed(\n mediaPath: string,\n localRoots: readonly string[] | undefined,\n): Promise<void> {\n if (!localRoots || localRoots.length === 0) {\n throw new Error(`Local media path is not under an allowed directory: ${mediaPath}`);\n }\n\n let resolved: string;\n try {\n resolved = await fs.realpath(mediaPath);\n } catch {\n resolved = path.resolve(mediaPath);\n }\n\n for (const root of localRoots) {\n let resolvedRoot: string;\n try {\n resolvedRoot = await fs.realpath(root);\n } catch {\n resolvedRoot = path.resolve(root);\n }\n if (resolvedRoot === path.parse(resolvedRoot).root) {\n continue;\n }\n if (resolved === resolvedRoot || resolved.startsWith(resolvedRoot + path.sep)) {\n return;\n }\n }\n\n throw new Error(`Local media path is not under an allowed directory: ${mediaPath}`);\n}\n\n/** 从远程 URL 获取媒体 */\nasync function fetchRemoteMedia(\n url: string,\n maxBytes?: number,\n): Promise<{ buffer: Buffer; contentType?: string; fileName?: string }> {\n const res = await fetch(url, { redirect: \"follow\" });\n if (!res.ok) {\n throw new Error(`Failed to fetch media from ${url}: HTTP ${res.status} ${res.statusText}`);\n }\n\n const buffer = Buffer.from(await res.arrayBuffer());\n if (maxBytes && buffer.length > maxBytes) {\n throw new Error(`Media from ${url} exceeds max size (${buffer.length} > ${maxBytes})`);\n }\n\n const headerMime = res.headers.get(\"content-type\")?.split(\";\")?.[0]?.trim();\n\n let fileName: string | undefined;\n const disposition = res.headers.get(\"content-disposition\");\n if (disposition) {\n const match = /filename\\*?\\s*=\\s*(?:UTF-8''|\")?([^\";]+)/i.exec(disposition);\n if (match?.[1]) {\n try {\n fileName = path.basename(decodeURIComponent(match[1].replace(/[\"']/g, \"\").trim()));\n } catch {\n fileName = path.basename(match[1].replace(/[\"']/g, \"\").trim());\n }\n }\n }\n if (!fileName) {\n try {\n const parsed = new URL(url);\n const base = path.basename(parsed.pathname);\n if (base && base.includes(\".\")) fileName = base;\n } catch { /* ignore */ }\n }\n\n const contentType = await detectMimeFallback({ buffer, headerMime, filePath: fileName ?? url });\n\n return { buffer, contentType, fileName };\n}\n\n/** 展开 ~ 为用户主目录 */\nfunction resolveUserPath(p: string): string {\n if (p.startsWith(\"~\")) {\n return path.join(os.homedir(), p.slice(1));\n }\n return p;\n}\n\n/** fallback 版 loadOutboundMediaFromUrl,参考 weclaw/src/web/media.ts */\nasync function loadOutboundMediaFromUrlFallback(\n mediaUrl: string,\n options: OutboundMediaLoadOptions = {},\n): Promise<WebMediaResult> {\n const { maxBytes, mediaLocalRoots } = options;\n\n // 去除 MEDIA: 前缀\n mediaUrl = mediaUrl.replace(/^\\s*MEDIA\\s*:\\s*/i, \"\");\n\n // 处理 file:// URL\n if (mediaUrl.startsWith(\"file://\")) {\n try {\n mediaUrl = fileURLToPath(mediaUrl);\n } catch {\n throw new Error(`Invalid file:// URL: ${mediaUrl}`);\n }\n }\n\n // 远程 URL\n if (/^https?:\\/\\//i.test(mediaUrl)) {\n const fetched = await fetchRemoteMedia(mediaUrl, maxBytes);\n return {\n buffer: fetched.buffer,\n contentType: fetched.contentType,\n fileName: fetched.fileName,\n };\n }\n\n // 展开 ~ 路径\n if (mediaUrl.startsWith(\"~\")) {\n mediaUrl = resolveUserPath(mediaUrl);\n }\n\n // 本地文件:安全校验\n await assertLocalMediaAllowed(mediaUrl, mediaLocalRoots);\n\n // 读取本地文件\n let data: Buffer;\n try {\n const stat = await fs.stat(mediaUrl);\n if (!stat.isFile()) {\n throw new Error(`Local media path is not a file: ${mediaUrl}`);\n }\n data = await fs.readFile(mediaUrl);\n } catch (err: any) {\n if (err?.code === \"ENOENT\") {\n throw new Error(`Local media file not found: ${mediaUrl}`);\n }\n throw err;\n }\n\n if (maxBytes && data.length > maxBytes) {\n throw new Error(`Local media exceeds max size (${data.length} > ${maxBytes})`);\n }\n\n const mime = await detectMimeFallback({ buffer: data, filePath: mediaUrl });\n const fileName = path.basename(mediaUrl) || undefined;\n\n return {\n buffer: data,\n contentType: mime,\n fileName,\n };\n}\n\n/**\n * 从 URL 或本地路径加载媒体文件(兼容入口)\n *\n * 优先使用 SDK 版本,不可用时使用 fallback。\n * SDK 版本抛出的业务异常(如 LocalMediaAccessError)会直接透传。\n */\nexport async function loadOutboundMediaFromUrl(\n mediaUrl: string,\n options: OutboundMediaLoadOptions = {},\n): Promise<WebMediaResult> {\n const sdk = await _sdkReady;\n\n if (sdk.loadOutboundMediaFromUrl) {\n return sdk.loadOutboundMediaFromUrl(mediaUrl, options);\n }\n return loadOutboundMediaFromUrlFallback(mediaUrl, options);\n}\n\n// ============================================================================\n// addWildcardAllowFrom —— 向 allowFrom 列表添加通配符 \"*\"\n// ============================================================================\n\n/** fallback 版 addWildcardAllowFrom:确保列表中包含 \"*\" 通配符 */\nfunction addWildcardAllowFromFallback(allowFrom: string[]): string[] {\n if (allowFrom.includes(\"*\")) {\n return allowFrom;\n }\n return [...allowFrom, \"*\"];\n}\n\n/**\n * 向 allowFrom 列表添加通配符 \"*\"(兼容入口)\n *\n * 当 dmPolicy 为 \"open\" 时,需要确保 allowFrom 中包含 \"*\" 以允许所有来源。\n * 优先使用 SDK 版本(plugin-sdk/setup 或 plugin-sdk/core),不可用时使用 fallback。\n *\n * 注意:此函数为同步函数,与 SDK 原始签名一致。\n * SDK 引用在模块加载时异步探测并缓存,调用时同步读取缓存。\n */\nexport function addWildcardAllowFrom(allowFrom: string[]): string[] {\n if (_cachedAddWildcardAllowFrom) {\n try {\n return _cachedAddWildcardAllowFrom(allowFrom);\n } catch {\n // SDK 版本异常,降级到 fallback\n }\n }\n return addWildcardAllowFromFallback(allowFrom);\n}\n\n// ============================================================================\n// getDefaultMediaLocalRoots —— 获取默认媒体本地路径白名单\n// ============================================================================\n\n/**\n * 获取默认媒体本地路径白名单(兼容入口)\n *\n * 优先使用 SDK 版本,不可用时手动构建白名单(与 weclaw/src/media/local-roots.ts 逻辑一致)。\n */\nexport async function getDefaultMediaLocalRoots(): Promise<readonly string[]> {\n const sdk = await _sdkReady;\n\n if (sdk.getDefaultMediaLocalRoots) {\n try {\n return sdk.getDefaultMediaLocalRoots();\n } catch {\n // SDK 版本异常,降级到 fallback\n }\n }\n\n // fallback: 手动构建默认白名单\n const stateDir = path.resolve(resolveStateDir());\n return [\n path.join(stateDir, \"media\"),\n path.join(stateDir, \"agents\"),\n path.join(stateDir, \"workspace\"),\n path.join(stateDir, \"sandboxes\"),\n ];\n}\n\nexport function emptyPluginConfigSchema(): Record<string, unknown> {\n return {\n type: 'object',\n additionalProperties: false,\n properties: {},\n };\n}\n\n// ============================================================================\n// 辅助函数(参考 moltbot 实现)\n// ============================================================================\n\n/**\n * 解析可选的分隔条目\n * @param value 输入字符串,支持逗号、分号、换行符分隔\n * @returns 解析后的字符串数组,如果输入为空则返回 undefined\n */\nexport function parseOptionalDelimitedEntries(value?: string): string[] | undefined {\n if (!value?.trim()) {\n return undefined;\n }\n const parsed = value\n .split(/[\\n,;]+/g)\n .map((entry) => entry.trim())\n .filter(Boolean);\n return parsed.length > 0 ? parsed : undefined;\n}\n\n/**\n * 构建账户范围的 DM 安全策略\n */\nexport function buildAccountScopedDmSecurityPolicy(params: {\n cfg: Record<string, unknown>;\n channelKey: string;\n accountId?: string | null;\n fallbackAccountId?: string | null;\n policy?: string | null;\n allowFrom?: Array<string | number> | null;\n defaultPolicy?: string;\n allowFromPathSuffix?: string;\n policyPathSuffix?: string;\n approveChannelId?: string;\n approveHint?: string;\n normalizeEntry?: (raw: string) => string;\n}): {\n policy: string;\n allowFrom: Array<string | number>;\n policyPath?: string;\n allowFromPath: string;\n approveHint: string;\n normalizeEntry?: (raw: string) => string;\n} {\n const DEFAULT_ACCOUNT_ID = \"default\";\n const resolvedAccountId = params.accountId ?? params.fallbackAccountId ?? DEFAULT_ACCOUNT_ID;\n const channelConfig = (params.cfg.channels as Record<string, unknown> | undefined)?.[\n params.channelKey\n ] as { accounts?: Record<string, unknown> } | undefined;\n const useAccountPath = Boolean(channelConfig?.accounts?.[resolvedAccountId]);\n const basePath = useAccountPath\n ? `channels.${params.channelKey}.accounts.${resolvedAccountId}.`\n : `channels.${params.channelKey}.`;\n const allowFromPath = `${basePath}${params.allowFromPathSuffix ?? \"\"}`;\n const policyPath =\n params.policyPathSuffix != null ? `${basePath}${params.policyPathSuffix}` : undefined;\n\n return {\n policy: params.policy ?? params.defaultPolicy ?? \"pairing\",\n allowFrom: params.allowFrom ?? [],\n policyPath,\n allowFromPath,\n approveHint:\n params.approveHint ?? formatPairingApproveHint(params.approveChannelId ?? params.channelKey),\n normalizeEntry: params.normalizeEntry,\n };\n}\n\n/**\n * 格式化配对审批提示信息(参考 moltbot 实现)\n * @param channelId 频道ID\n * @returns 配对审批提示字符串\n */\nexport function formatPairingApproveHint(channelId: string): string {\n const listCmd = `openclaw pairing list ${channelId}`;\n const approveCmd = `openclaw pairing approve ${channelId} <code>`;\n return `向 ${channelId} 机器人发送消息以完成配对审批(命令行:${listCmd} / ${approveCmd})`;\n}\n"],"names":["exports"],"mappings":"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"}
@@ -9,15 +9,15 @@ const DEFAULT_MEMORY_MAX_SIZE = 200;
9
9
  // ============================================================================
10
10
  // 核心实现
11
11
  // ============================================================================
12
- export function createPersistentReqIdStore(accountId, options) {
13
- const ttlMs = options?.ttlMs ?? DEFAULT_TTL_MS;
14
- const memoryMaxSize = options?.memoryMaxSize ?? DEFAULT_MEMORY_MAX_SIZE;
12
+ function createPersistentReqIdStore(accountId, options) {
13
+ const ttlMs = DEFAULT_TTL_MS;
14
+ const memoryMaxSize = DEFAULT_MEMORY_MAX_SIZE;
15
15
  // 内存层:chatId → ReqIdEntry
16
16
  const memory = new Map();
17
17
  // ========== 内部辅助函数 ==========
18
18
  /** 检查条目是否过期 */
19
19
  function isExpired(entry, now) {
20
- return ttlMs > 0 && now - entry.ts >= ttlMs;
20
+ return now - entry.ts >= ttlMs;
21
21
  }
22
22
  /**
23
23
  * 内存容量控制:淘汰最旧的条目。
@@ -81,3 +81,6 @@ export function createPersistentReqIdStore(accountId, options) {
81
81
  memorySize,
82
82
  };
83
83
  }
84
+
85
+ export { createPersistentReqIdStore };
86
+ //# sourceMappingURL=reqid-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reqid-store.js","sources":["../../../../../src/reqid-store.ts"],"sourcesContent":["// ============================================================================\n// 类型定义\n// ============================================================================\n\n/** 单条 reqId 记录 */\ninterface ReqIdEntry {\n /** 请求 ID */\n reqId: string;\n /** 记录时间戳(毫秒) */\n ts: number;\n}\n\n/** Store 配置 */\ninterface ReqIdStoreOptions {\n /** TTL 毫秒数,超时的 reqId 视为过期(默认 7 天) */\n ttlMs?: number;\n /** 内存最大条目数(默认 200) */\n memoryMaxSize?: number;\n}\n\n// ============================================================================\n// 常量\n// ============================================================================\n\nconst DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 天\nconst DEFAULT_MEMORY_MAX_SIZE = 200;\n\n// ============================================================================\n// 公开接口\n// ============================================================================\n\nexport interface PersistentReqIdStore {\n /** 设置 chatId 对应的 reqId(仅写入内存) */\n set(chatId: string, reqId: string): void;\n /** 获取 chatId 对应的 reqId(仅内存) */\n get(chatId: string): Promise<string | undefined>;\n /** 同步获取 chatId 对应的 reqId(仅内存) */\n getSync(chatId: string): string | undefined;\n /** 删除 chatId 对应的 reqId */\n delete(chatId: string): void;\n /** 清空内存缓存 */\n clearMemory(): void;\n /** 返回内存中的条目数 */\n memorySize(): number;\n}\n\n// ============================================================================\n// 核心实现\n// ============================================================================\n\nexport function createPersistentReqIdStore(\n accountId: string,\n options?: ReqIdStoreOptions,\n): PersistentReqIdStore {\n const ttlMs = options?.ttlMs ?? DEFAULT_TTL_MS;\n const memoryMaxSize = options?.memoryMaxSize ?? DEFAULT_MEMORY_MAX_SIZE;\n\n // 内存层:chatId → ReqIdEntry\n const memory = new Map<string, ReqIdEntry>();\n\n // ========== 内部辅助函数 ==========\n\n /** 检查条目是否过期 */\n function isExpired(entry: ReqIdEntry, now: number): boolean {\n return ttlMs > 0 && now - entry.ts >= ttlMs;\n }\n\n /**\n * 内存容量控制:淘汰最旧的条目。\n * 利用 Map 的插入顺序 + touch(先 delete 再 set) 实现类 LRU 效果。\n */\n function pruneMemory(): void {\n if (memory.size <= memoryMaxSize) return;\n const sorted = [...memory.entries()].sort((a, b) => a[1].ts - b[1].ts);\n const toRemove = sorted.slice(0, memory.size - memoryMaxSize);\n for (const [key] of toRemove) {\n memory.delete(key);\n }\n }\n\n // ========== 公开 API ==========\n\n function set(chatId: string, reqId: string): void {\n const entry: ReqIdEntry = { reqId, ts: Date.now() };\n // touch:先删再设,保持 Map 插入顺序(类 LRU)\n memory.delete(chatId);\n memory.set(chatId, entry);\n pruneMemory();\n }\n\n async function get(chatId: string): Promise<string | undefined> {\n const now = Date.now();\n\n // 仅查内存\n const memEntry = memory.get(chatId);\n if (memEntry && !isExpired(memEntry, now)) {\n return memEntry.reqId;\n }\n if (memEntry) {\n memory.delete(chatId); // 过期则删除\n }\n\n return undefined;\n }\n\n function getSync(chatId: string): string | undefined {\n const now = Date.now();\n const entry = memory.get(chatId);\n if (entry && !isExpired(entry, now)) {\n return entry.reqId;\n }\n if (entry) {\n memory.delete(chatId);\n }\n return undefined;\n }\n\n function del(chatId: string): void {\n memory.delete(chatId);\n }\n\n function clearMemory(): void {\n memory.clear();\n }\n\n function memorySize(): number {\n return memory.size;\n }\n\n return {\n set,\n get,\n getSync,\n delete: del,\n clearMemory,\n memorySize,\n };\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AAkBA;AACA;AACA;AAEA,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAC/C,MAAM,uBAAuB,GAAG,GAAG;AAqBnC;AACA;AACA;AAEM,SAAU,0BAA0B,CACxC,SAAiB,EACjB,OAA2B,EAAA;AAE3B,IAAA,MAAM,KAAK,GAAqB,cAAc;AAC9C,IAAA,MAAM,aAAa,GAA6B,uBAAuB;;AAGvE,IAAA,MAAM,MAAM,GAAG,IAAI,GAAG,EAAsB;;;AAK5C,IAAA,SAAS,SAAS,CAAC,KAAiB,EAAE,GAAW,EAAA;QAC/C,OAAoB,GAAG,GAAG,KAAK,CAAC,EAAE,IAAI,KAAK;IAC7C;AAEA;;;AAGG;AACH,IAAA,SAAS,WAAW,GAAA;AAClB,QAAA,IAAI,MAAM,CAAC,IAAI,IAAI,aAAa;YAAE;AAClC,QAAA,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACtE,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,GAAG,aAAa,CAAC;AAC7D,QAAA,KAAK,MAAM,CAAC,GAAG,CAAC,IAAI,QAAQ,EAAE;AAC5B,YAAA,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;QACpB;IACF;;AAIA,IAAA,SAAS,GAAG,CAAC,MAAc,EAAE,KAAa,EAAA;AACxC,QAAA,MAAM,KAAK,GAAe,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE;;AAEnD,QAAA,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;AACrB,QAAA,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC;AACzB,QAAA,WAAW,EAAE;IACf;IAEA,eAAe,GAAG,CAAC,MAAc,EAAA;AAC/B,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;;QAGtB,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;QACnC,IAAI,QAAQ,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE;YACzC,OAAO,QAAQ,CAAC,KAAK;QACvB;QACA,IAAI,QAAQ,EAAE;AACZ,YAAA,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxB;AAEA,QAAA,OAAO,SAAS;IAClB;IAEA,SAAS,OAAO,CAAC,MAAc,EAAA;AAC7B,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACtB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;QAChC,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE;YACnC,OAAO,KAAK,CAAC,KAAK;QACpB;QACA,IAAI,KAAK,EAAE;AACT,YAAA,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;QACvB;AACA,QAAA,OAAO,SAAS;IAClB;IAEA,SAAS,GAAG,CAAC,MAAc,EAAA;AACzB,QAAA,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;IACvB;AAEA,IAAA,SAAS,WAAW,GAAA;QAClB,MAAM,CAAC,KAAK,EAAE;IAChB;AAEA,IAAA,SAAS,UAAU,GAAA;QACjB,OAAO,MAAM,CAAC,IAAI;IACpB;IAEA,OAAO;QACL,GAAG;QACH,GAAG;QACH,OAAO;AACP,QAAA,MAAM,EAAE,GAAG;QACX,WAAW;QACX,UAAU;KACX;AACH;;;;"}
@@ -1,10 +1,13 @@
1
1
  let runtime = null;
2
- export function setWeComRuntime(r) {
2
+ function setWeComRuntime(r) {
3
3
  runtime = r;
4
4
  }
5
- export function getWeComRuntime() {
5
+ function getWeComRuntime() {
6
6
  if (!runtime) {
7
7
  throw new Error("WeCom runtime not initialized - plugin not registered");
8
8
  }
9
9
  return runtime;
10
10
  }
11
+
12
+ export { getWeComRuntime, setWeComRuntime };
13
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sources":["../../../../../src/runtime.ts"],"sourcesContent":["import type { PluginRuntime } from \"openclaw/plugin-sdk/core\";\n\nlet runtime: PluginRuntime | null = null;\n\nexport function setWeComRuntime(r: PluginRuntime): void {\n runtime = r;\n}\n\nexport function getWeComRuntime(): PluginRuntime {\n if (!runtime) {\n throw new Error(\"WeCom runtime not initialized - plugin not registered\");\n }\n return runtime;\n}\n"],"names":[],"mappings":"AAEA,IAAI,OAAO,GAAyB,IAAI;AAElC,SAAU,eAAe,CAAC,CAAgB,EAAA;IAC9C,OAAO,GAAG,CAAC;AACb;SAEgB,eAAe,GAAA;IAC7B,IAAI,CAAC,OAAO,EAAE;AACZ,QAAA,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC;IAC1E;AACA,IAAA,OAAO,OAAO;AAChB;;;;"}
@@ -0,0 +1,13 @@
1
+ import * as path from 'node:path';
2
+ import * as os from 'node:os';
3
+
4
+ /** 解析 openclaw 状态目录 */
5
+ function resolveStateDir() {
6
+ const stateOverride = process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim();
7
+ if (stateOverride)
8
+ return stateOverride;
9
+ return path.join(os.homedir(), ".openclaw");
10
+ }
11
+
12
+ export { resolveStateDir };
13
+ //# sourceMappingURL=state-dir-resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-dir-resolve.js","sources":["../../../../../src/state-dir-resolve.ts"],"sourcesContent":["import * as path from \"node:path\";\nimport * as os from \"node:os\";\n\n/** 解析 openclaw 状态目录 */\nexport function resolveStateDir(): string {\n const stateOverride = process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim();\n if (stateOverride) return stateOverride;\n return path.join(os.homedir(), \".openclaw\");\n}\n"],"names":[],"mappings":";;;AAGA;SACgB,eAAe,GAAA;AAC7B,IAAA,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE;AACtG,IAAA,IAAI,aAAa;AAAE,QAAA,OAAO,aAAa;IACvC,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC;AAC7C;;;;"}
@@ -1,11 +1,12 @@
1
+ import { createPersistentReqIdStore } from './reqid-store.js';
2
+ import { MESSAGE_STATE_CLEANUP_INTERVAL_MS, MESSAGE_STATE_TTL_MS, MESSAGE_STATE_MAX_SIZE } from './const.js';
3
+
1
4
  /**
2
5
  * 企业微信全局状态管理模块
3
6
  *
4
7
  * 负责管理 WSClient 实例、消息状态(带 TTL 清理)、ReqId 存储
5
8
  * 解决全局 Map 的内存泄漏问题
6
9
  */
7
- import { createPersistentReqIdStore } from "./reqid-store.js";
8
- import { MESSAGE_STATE_TTL_MS, MESSAGE_STATE_CLEANUP_INTERVAL_MS, MESSAGE_STATE_MAX_SIZE, } from "./const.js";
9
10
  // ============================================================================
10
11
  // WSClient 实例管理
11
12
  // ============================================================================
@@ -14,21 +15,15 @@ const wsClientInstances = new Map();
14
15
  /**
15
16
  * 获取指定账户的 WSClient 实例
16
17
  */
17
- export function getWeComWebSocket(accountId) {
18
+ function getWeComWebSocket(accountId) {
18
19
  return wsClientInstances.get(accountId) ?? null;
19
20
  }
20
21
  /**
21
22
  * 设置指定账户的 WSClient 实例
22
23
  */
23
- export function setWeComWebSocket(accountId, client) {
24
+ function setWeComWebSocket(accountId, client) {
24
25
  wsClientInstances.set(accountId, client);
25
26
  }
26
- /**
27
- * 删除指定账户的 WSClient 实例
28
- */
29
- export function deleteWeComWebSocket(accountId) {
30
- wsClientInstances.delete(accountId);
31
- }
32
27
  /** 消息状态管理 */
33
28
  const messageStates = new Map();
34
29
  /** 定期清理定时器 */
@@ -36,7 +31,7 @@ let cleanupTimer = null;
36
31
  /**
37
32
  * 启动消息状态定期清理(自动 TTL 清理 + 容量限制)
38
33
  */
39
- export function startMessageStateCleanup() {
34
+ function startMessageStateCleanup() {
40
35
  if (cleanupTimer)
41
36
  return;
42
37
  cleanupTimer = setInterval(() => {
@@ -50,7 +45,7 @@ export function startMessageStateCleanup() {
50
45
  /**
51
46
  * 停止消息状态定期清理
52
47
  */
53
- export function stopMessageStateCleanup() {
48
+ function stopMessageStateCleanup() {
54
49
  if (cleanupTimer) {
55
50
  clearInterval(cleanupTimer);
56
51
  cleanupTimer = null;
@@ -79,38 +74,18 @@ function pruneMessageStates() {
79
74
  /**
80
75
  * 设置消息状态
81
76
  */
82
- export function setMessageState(messageId, state) {
77
+ function setMessageState(messageId, state) {
83
78
  messageStates.set(messageId, {
84
79
  state,
85
80
  createdAt: Date.now(),
86
81
  });
87
82
  }
88
- /**
89
- * 获取消息状态
90
- */
91
- export function getMessageState(messageId) {
92
- const entry = messageStates.get(messageId);
93
- if (!entry)
94
- return undefined;
95
- // 检查 TTL
96
- if (Date.now() - entry.createdAt >= MESSAGE_STATE_TTL_MS) {
97
- messageStates.delete(messageId);
98
- return undefined;
99
- }
100
- return entry.state;
101
- }
102
83
  /**
103
84
  * 删除消息状态
104
85
  */
105
- export function deleteMessageState(messageId) {
86
+ function deleteMessageState(messageId) {
106
87
  messageStates.delete(messageId);
107
88
  }
108
- /**
109
- * 清空所有消息状态
110
- */
111
- export function clearAllMessageStates() {
112
- messageStates.clear();
113
- }
114
89
  // ============================================================================
115
90
  // ReqId 持久化存储管理(按 accountId 隔离)
116
91
  // ============================================================================
@@ -123,7 +98,7 @@ const reqIdStores = new Map();
123
98
  function getOrCreateReqIdStore(accountId) {
124
99
  let store = reqIdStores.get(accountId);
125
100
  if (!store) {
126
- store = createPersistentReqIdStore(accountId);
101
+ store = createPersistentReqIdStore();
127
102
  reqIdStores.set(accountId, store);
128
103
  }
129
104
  return store;
@@ -134,52 +109,26 @@ function getOrCreateReqIdStore(accountId) {
134
109
  /**
135
110
  * 设置 chatId 对应的 reqId(写入内存 + 防抖写磁盘)
136
111
  */
137
- export function setReqIdForChat(chatId, reqId, accountId = "default") {
112
+ function setReqIdForChat(chatId, reqId, accountId = "default") {
138
113
  getOrCreateReqIdStore(accountId).set(chatId, reqId);
139
114
  }
140
- /**
141
- * 获取 chatId 对应的 reqId(异步:优先内存,miss 时查磁盘并回填内存)
142
- */
143
- export async function getReqIdForChatAsync(chatId, accountId = "default") {
144
- return getOrCreateReqIdStore(accountId).get(chatId);
145
- }
146
- /**
147
- * 获取 chatId 对应的 reqId(同步:仅内存,保留向后兼容)
148
- */
149
- export function getReqIdForChat(chatId, accountId = "default") {
150
- return getOrCreateReqIdStore(accountId).getSync(chatId);
151
- }
152
- /**
153
- * 删除 chatId 对应的 reqId
154
- */
155
- export function deleteReqIdForChat(chatId, accountId = "default") {
156
- getOrCreateReqIdStore(accountId).delete(chatId);
157
- }
158
115
  /**
159
116
  * 启动时预热 reqId 缓存(从磁盘加载到内存)
160
117
  *
161
118
  * 注意:由于移除了磁盘存储,此函数现在只返回 0(无预热条目)
162
119
  */
163
- export async function warmupReqIdStore(accountId = "default", log) {
120
+ async function warmupReqIdStore(accountId = "default", log) {
164
121
  // 由于移除了磁盘存储,不再需要预热过程
165
122
  log?.("[WeCom] reqid-store warmup: no-op (disk storage removed)");
166
123
  return 0;
167
124
  }
168
- /**
169
- * 立即将 reqId 数据刷写到磁盘(用于优雅退出)
170
- *
171
- * 注意:由于移除了磁盘存储,此函数现在是无操作
172
- */
173
- export async function flushReqIdStore(accountId = "default") {
174
- // 由于移除了磁盘存储,不再需要刷写操作
175
- }
176
125
  // ============================================================================
177
126
  // 全局 cleanup(断开连接时释放所有资源)
178
127
  // ============================================================================
179
128
  /**
180
129
  * 清理指定账户的所有资源
181
130
  */
182
- export async function cleanupAccount(accountId) {
131
+ async function cleanupAccount(accountId) {
183
132
  // 1. 断开 WSClient
184
133
  const wsClient = wsClientInstances.get(accountId);
185
134
  if (wsClient) {
@@ -194,23 +143,6 @@ export async function cleanupAccount(accountId) {
194
143
  // 2. 由于移除了磁盘存储,不再需要 flush reqId 存储
195
144
  // 注意:不删除 store,因为重连后可能还需要
196
145
  }
197
- /**
198
- * 清理所有资源(用于进程退出)
199
- */
200
- export async function cleanupAll() {
201
- // 停止定期清理
202
- stopMessageStateCleanup();
203
- // 清理所有 WSClient
204
- for (const [accountId, wsClient] of wsClientInstances) {
205
- try {
206
- wsClient.disconnect();
207
- }
208
- catch {
209
- // 忽略
210
- }
211
- }
212
- wsClientInstances.clear();
213
- // 由于移除了磁盘存储,不再需要 flush 所有 reqId 存储
214
- // 清空消息状态
215
- clearAllMessageStates();
216
- }
146
+
147
+ export { cleanupAccount, deleteMessageState, getWeComWebSocket, setMessageState, setReqIdForChat, setWeComWebSocket, startMessageStateCleanup, stopMessageStateCleanup, warmupReqIdStore };
148
+ //# sourceMappingURL=state-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-manager.js","sources":["../../../../../src/state-manager.ts"],"sourcesContent":["/**\n * 企业微信全局状态管理模块\n *\n * 负责管理 WSClient 实例、消息状态(带 TTL 清理)、ReqId 存储\n * 解决全局 Map 的内存泄漏问题\n */\n\nimport type { WSClient } from \"@wecom/aibot-node-sdk\";\nimport type { MessageState } from \"./interface.js\";\nimport { createPersistentReqIdStore, type PersistentReqIdStore } from \"./reqid-store.js\";\nimport {\n MESSAGE_STATE_TTL_MS,\n MESSAGE_STATE_CLEANUP_INTERVAL_MS,\n MESSAGE_STATE_MAX_SIZE,\n} from \"./const.js\";\n\n// ============================================================================\n// WSClient 实例管理\n// ============================================================================\n\n/** WSClient 实例管理 */\nconst wsClientInstances = new Map<string, WSClient>();\n\n/**\n * 获取指定账户的 WSClient 实例\n */\nexport function getWeComWebSocket(accountId: string): WSClient | null {\n return wsClientInstances.get(accountId) ?? null;\n}\n\n/**\n * 设置指定账户的 WSClient 实例\n */\nexport function setWeComWebSocket(accountId: string, client: WSClient): void {\n wsClientInstances.set(accountId, client);\n}\n\n/**\n * 删除指定账户的 WSClient 实例\n */\nexport function deleteWeComWebSocket(accountId: string): void {\n wsClientInstances.delete(accountId);\n}\n\n// ============================================================================\n// 消息状态管理(带 TTL 清理,防止内存泄漏)\n// ============================================================================\n\n/** 消息状态条目(带创建时间戳,用于 TTL 清理) */\ninterface MessageStateEntry {\n state: MessageState;\n createdAt: number;\n}\n\n/** 消息状态管理 */\nconst messageStates = new Map<string, MessageStateEntry>();\n\n/** 定期清理定时器 */\nlet cleanupTimer: ReturnType<typeof setInterval> | null = null;\n\n/**\n * 启动消息状态定期清理(自动 TTL 清理 + 容量限制)\n */\nexport function startMessageStateCleanup(): void {\n if (cleanupTimer) return;\n\n cleanupTimer = setInterval(() => {\n pruneMessageStates();\n }, MESSAGE_STATE_CLEANUP_INTERVAL_MS);\n\n // 允许进程退出时不阻塞\n if (cleanupTimer && typeof cleanupTimer === \"object\" && \"unref\" in cleanupTimer) {\n cleanupTimer.unref();\n }\n}\n\n/**\n * 停止消息状态定期清理\n */\nexport function stopMessageStateCleanup(): void {\n if (cleanupTimer) {\n clearInterval(cleanupTimer);\n cleanupTimer = null;\n }\n}\n\n/**\n * 清理过期和超量的消息状态条目\n */\nfunction pruneMessageStates(): void {\n const now = Date.now();\n\n // 1. 清理过期条目\n for (const [key, entry] of messageStates) {\n if (now - entry.createdAt >= MESSAGE_STATE_TTL_MS) {\n messageStates.delete(key);\n }\n }\n\n // 2. 容量限制:如果仍超过最大条目数,按时间淘汰最旧的\n if (messageStates.size > MESSAGE_STATE_MAX_SIZE) {\n const sorted = [...messageStates.entries()].sort((a, b) => a[1].createdAt - b[1].createdAt);\n const toRemove = sorted.slice(0, messageStates.size - MESSAGE_STATE_MAX_SIZE);\n for (const [key] of toRemove) {\n messageStates.delete(key);\n }\n }\n}\n\n/**\n * 设置消息状态\n */\nexport function setMessageState(messageId: string, state: MessageState): void {\n messageStates.set(messageId, {\n state,\n createdAt: Date.now(),\n });\n}\n\n/**\n * 获取消息状态\n */\nexport function getMessageState(messageId: string): MessageState | undefined {\n const entry = messageStates.get(messageId);\n if (!entry) return undefined;\n\n // 检查 TTL\n if (Date.now() - entry.createdAt >= MESSAGE_STATE_TTL_MS) {\n messageStates.delete(messageId);\n return undefined;\n }\n return entry.state;\n}\n\n/**\n * 删除消息状态\n */\nexport function deleteMessageState(messageId: string): void {\n messageStates.delete(messageId);\n}\n\n/**\n * 清空所有消息状态\n */\nexport function clearAllMessageStates(): void {\n messageStates.clear();\n}\n\n// ============================================================================\n// ReqId 持久化存储管理(按 accountId 隔离)\n// ============================================================================\n\n/**\n * ReqId 持久化存储管理\n * 参考 createPersistentDedupe 模式:内存 + 磁盘双层、文件锁、原子写入、TTL 过期、防抖写入\n * 重启后可从磁盘恢复,确保主动推送消息时能获取到 reqId\n */\nconst reqIdStores = new Map<string, PersistentReqIdStore>();\n\nfunction getOrCreateReqIdStore(accountId: string): PersistentReqIdStore {\n let store = reqIdStores.get(accountId);\n if (!store) {\n store = createPersistentReqIdStore(accountId);\n reqIdStores.set(accountId, store);\n }\n return store;\n}\n\n// ============================================================================\n// ReqId 操作函数\n// ============================================================================\n\n/**\n * 设置 chatId 对应的 reqId(写入内存 + 防抖写磁盘)\n */\nexport function setReqIdForChat(chatId: string, reqId: string, accountId = \"default\"): void {\n getOrCreateReqIdStore(accountId).set(chatId, reqId);\n}\n\n/**\n * 获取 chatId 对应的 reqId(异步:优先内存,miss 时查磁盘并回填内存)\n */\nexport async function getReqIdForChatAsync(chatId: string, accountId = \"default\"): Promise<string | undefined> {\n return getOrCreateReqIdStore(accountId).get(chatId);\n}\n\n/**\n * 获取 chatId 对应的 reqId(同步:仅内存,保留向后兼容)\n */\nexport function getReqIdForChat(chatId: string, accountId = \"default\"): string | undefined {\n return getOrCreateReqIdStore(accountId).getSync(chatId);\n}\n\n/**\n * 删除 chatId 对应的 reqId\n */\nexport function deleteReqIdForChat(chatId: string, accountId = \"default\"): void {\n getOrCreateReqIdStore(accountId).delete(chatId);\n}\n\n/**\n * 启动时预热 reqId 缓存(从磁盘加载到内存)\n * \n * 注意:由于移除了磁盘存储,此函数现在只返回 0(无预热条目)\n */\nexport async function warmupReqIdStore(\n accountId = \"default\",\n log?: (...args: unknown[]) => void,\n): Promise<number> {\n // 由于移除了磁盘存储,不再需要预热过程\n log?.(\"[WeCom] reqid-store warmup: no-op (disk storage removed)\");\n return 0;\n}\n\n/**\n * 立即将 reqId 数据刷写到磁盘(用于优雅退出)\n * \n * 注意:由于移除了磁盘存储,此函数现在是无操作\n */\nexport async function flushReqIdStore(accountId = \"default\"): Promise<void> {\n // 由于移除了磁盘存储,不再需要刷写操作\n}\n\n// ============================================================================\n// 全局 cleanup(断开连接时释放所有资源)\n// ============================================================================\n\n/**\n * 清理指定账户的所有资源\n */\nexport async function cleanupAccount(accountId: string): Promise<void> {\n // 1. 断开 WSClient\n const wsClient = wsClientInstances.get(accountId);\n if (wsClient) {\n try {\n wsClient.disconnect();\n } catch {\n // 忽略断开连接时的错误\n }\n wsClientInstances.delete(accountId);\n }\n\n // 2. 由于移除了磁盘存储,不再需要 flush reqId 存储\n // 注意:不删除 store,因为重连后可能还需要\n}\n\n/**\n * 清理所有资源(用于进程退出)\n */\nexport async function cleanupAll(): Promise<void> {\n // 停止定期清理\n stopMessageStateCleanup();\n\n // 清理所有 WSClient\n for (const [accountId, wsClient] of wsClientInstances) {\n try {\n wsClient.disconnect();\n } catch {\n // 忽略\n }\n }\n wsClientInstances.clear();\n\n // 由于移除了磁盘存储,不再需要 flush 所有 reqId 存储\n\n // 清空消息状态\n clearAllMessageStates();\n}\n"],"names":[],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import { VALID_CARD_TYPES } from './const.js';
2
+
1
3
  /**
2
4
  * 模板卡片解析器
3
5
  *
@@ -7,7 +9,6 @@
7
9
  * 同时提供 maskTemplateCardBlocks 函数,用于在流式中间帧中隐藏正在构建的卡片代码块,
8
10
  * 避免 JSON 源码暴露给终端用户。
9
11
  */
10
- import { VALID_CARD_TYPES } from "./const.js";
11
12
  // ============================================================================
12
13
  // LLM 输出字段类型修正
13
14
  // ============================================================================
@@ -238,19 +239,6 @@ function normalizeTemplateCardFields(card, log) {
238
239
  }
239
240
  return card;
240
241
  }
241
- // ============================================================================
242
- // 必填字段校验与自动补全
243
- // ============================================================================
244
- /** task_id 合法字符:数字、字母、_-@ */
245
- const TASK_ID_RE = /^[a-zA-Z0-9_\-@]+$/;
246
- /**
247
- * 生成一个合法的 task_id。
248
- * 格式:task_{cardType}_{timestamp}_{随机4位},确保唯一且符合 API 要求。
249
- */
250
- function generateTaskId(cardType) {
251
- const rand = Math.random().toString(36).slice(2, 6);
252
- return `task_${cardType}_${Date.now()}_${rand}`;
253
- }
254
242
  /**
255
243
  * 校验并补全模板卡片的必填字段。
256
244
  *
@@ -545,7 +533,7 @@ const UNCLOSED_BLOCK_RE = /```(?:json)?\s*\n[\s\S]*$/;
545
533
  * @param text - LLM 回复的完整文本
546
534
  * @returns 提取结果,包含卡片列表和剩余文本
547
535
  */
548
- export function extractTemplateCards(text, log) {
536
+ function extractTemplateCards(text, log) {
549
537
  const cards = [];
550
538
  /** 需要从原文中移除的代码块(记录完整匹配内容) */
551
539
  const blocksToRemove = [];
@@ -613,7 +601,7 @@ export function extractTemplateCards(text, log) {
613
601
  * @param text - 当前累积文本
614
602
  * @returns 遮罩后的展示文本
615
603
  */
616
- export function maskTemplateCardBlocks(text, log) {
604
+ function maskTemplateCardBlocks(text, log) {
617
605
  let masked = text;
618
606
  let closedMaskCount = 0;
619
607
  let unclosedMasked = false;
@@ -641,7 +629,10 @@ export function maskTemplateCardBlocks(text, log) {
641
629
  }
642
630
  // 有遮罩行为时才打日志,避免每帧都刷屏
643
631
  if (closedMaskCount > 0 || unclosedMasked) {
644
- // log?.(`[template-card-parser] maskTemplateCardBlocks: closedMasked=${closedMaskCount}, unclosedMasked=${unclosedMasked}, textLength=${text.length}, maskedLength=${masked.length}`);
632
+ log?.(`[template-card-parser] maskTemplateCardBlocks: closedMasked=${closedMaskCount}, unclosedMasked=${unclosedMasked}, textLength=${text.length}, maskedLength=${masked.length}`);
645
633
  }
646
634
  return masked;
647
635
  }
636
+
637
+ export { extractTemplateCards, maskTemplateCardBlocks };
638
+ //# sourceMappingURL=template-card-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-card-parser.js","sources":["../../../../../src/template-card-parser.ts"],"sourcesContent":["/**\n * 模板卡片解析器\n *\n * 从 LLM 回复文本中提取 markdown JSON 代码块,验证其是否为合法的企业微信模板卡片,\n * 返回提取到的卡片列表和剩余文本。\n *\n * 同时提供 maskTemplateCardBlocks 函数,用于在流式中间帧中隐藏正在构建的卡片代码块,\n * 避免 JSON 源码暴露给终端用户。\n */\n\nimport { VALID_CARD_TYPES } from \"./const.js\";\nimport type { ExtractedTemplateCard, TemplateCardExtractionResult } from \"./interface.js\";\n\n// ============================================================================\n// LLM 输出字段类型修正\n// ============================================================================\n\n/**\n * 将 LLM 可能输出的字符串/非法值修正为企业微信 API 要求的整数。\n * 返回修正后的整数,若无法识别则返回 undefined(由调用方决定是否删除该字段)。\n */\nfunction coerceToInt(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return Math.round(value);\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim().toLowerCase();\n // 纯数字字符串\n const num = Number(trimmed);\n if (!Number.isNaN(num) && Number.isFinite(num)) {\n return Math.round(num);\n }\n }\n return undefined;\n}\n\n/** 将 LLM 可能输出的字符串/非法值修正为布尔值 */\nfunction coerceToBool(value: unknown): boolean | undefined {\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"string\") {\n const t = value.trim().toLowerCase();\n if (t === \"true\" || t === \"1\" || t === \"yes\") return true;\n if (t === \"false\" || t === \"0\" || t === \"no\") return false;\n }\n if (typeof value === \"number\") return value !== 0;\n return undefined;\n}\n\n/** checkbox.mode 的语义别名映射 */\nconst MODE_ALIASES: Record<string, number> = {\n single: 0,\n radio: 0,\n \"单选\": 0,\n multi: 1,\n multiple: 1,\n \"多选\": 1,\n};\n\n/**\n * 修正 checkbox.mode:\n * - 0 → 单选,1 → 多选,仅允许这两个值\n * - 字符串数字 \"0\"/\"1\" → 整数\n * - 语义别名 \"multi\"/\"single\" 等 → 对应整数\n * - 其他正整数(如 2)→ clamp 到 1(多选)\n * - 无法识别 → 删除(让服务端使用默认值 0)\n */\nfunction coerceCheckboxMode(value: unknown): number | undefined {\n let num: number | undefined;\n if (typeof value === \"number\" && Number.isFinite(value)) {\n num = Math.round(value);\n } else if (typeof value === \"string\") {\n const trimmed = value.trim().toLowerCase();\n if (trimmed in MODE_ALIASES) return MODE_ALIASES[trimmed];\n const parsed = Number(trimmed);\n if (!Number.isNaN(parsed)) num = Math.round(parsed);\n }\n if (num === undefined) return undefined;\n // mode 只允许 0(单选)或 1(多选),超出范围 clamp\n if (num <= 0) return 0;\n return 1;\n}\n\n/**\n * 对 LLM 生成的模板卡片 JSON 做字段类型修正,确保符合企业微信 API 的类型要求。\n *\n * 修正范围:\n * - checkbox.mode: uint32(0=单选,1=多选)\n * - checkbox.disable: bool\n * - checkbox.option_list[].is_checked: bool\n * - source.desc_color: int\n * - quote_area.type: int\n * - card_action.type: int\n * - image_text_area.type: int\n * - horizontal_content_list[].type: int\n * - jump_list[].type: int\n * - button_list[].style: int\n * - button_selection.disable: bool\n * - select_list[].disable: bool\n *\n * 原则:能修就修,修不了就删(让服务端走默认值),绝不阻塞发送。\n */\nfunction normalizeTemplateCardFields(\n card: Record<string, unknown>,\n log?: (...args: any[]) => void,\n): Record<string, unknown> {\n const fixes: string[] = [];\n\n // ── checkbox ──────────────────────────────────────────────────────────\n const checkbox = card.checkbox as Record<string, unknown> | undefined;\n if (checkbox && typeof checkbox === \"object\") {\n // mode\n if (\"mode\" in checkbox) {\n const fixed = coerceCheckboxMode(checkbox.mode);\n if (fixed !== undefined) {\n if (checkbox.mode !== fixed) {\n fixes.push(`checkbox.mode: ${JSON.stringify(checkbox.mode)} → ${fixed}`);\n }\n checkbox.mode = fixed;\n } else {\n fixes.push(`checkbox.mode: ${JSON.stringify(checkbox.mode)} → (deleted, invalid)`);\n delete checkbox.mode;\n }\n }\n // disable\n if (\"disable\" in checkbox) {\n const fixed = coerceToBool(checkbox.disable);\n if (fixed !== undefined && checkbox.disable !== fixed) {\n fixes.push(`checkbox.disable: ${JSON.stringify(checkbox.disable)} → ${fixed}`);\n checkbox.disable = fixed;\n }\n }\n // option_list[].is_checked\n if (Array.isArray(checkbox.option_list)) {\n for (const opt of checkbox.option_list as Record<string, unknown>[]) {\n if (opt && typeof opt === \"object\" && \"is_checked\" in opt) {\n const fixed = coerceToBool(opt.is_checked);\n if (fixed !== undefined && opt.is_checked !== fixed) {\n fixes.push(`checkbox.option_list.is_checked: ${JSON.stringify(opt.is_checked)} → ${fixed}`);\n opt.is_checked = fixed;\n }\n }\n }\n }\n }\n\n // ── source.desc_color ────────────────────────────────────────────────\n const source = card.source as Record<string, unknown> | undefined;\n if (source && typeof source === \"object\" && \"desc_color\" in source) {\n const fixed = coerceToInt(source.desc_color);\n if (fixed !== undefined && source.desc_color !== fixed) {\n fixes.push(`source.desc_color: ${JSON.stringify(source.desc_color)} → ${fixed}`);\n source.desc_color = fixed;\n }\n }\n\n // ── card_action.type ─────────────────────────────────────────────────\n const cardAction = card.card_action as Record<string, unknown> | undefined;\n if (cardAction && typeof cardAction === \"object\" && \"type\" in cardAction) {\n const fixed = coerceToInt(cardAction.type);\n if (fixed !== undefined && cardAction.type !== fixed) {\n fixes.push(`card_action.type: ${JSON.stringify(cardAction.type)} → ${fixed}`);\n cardAction.type = fixed;\n }\n }\n\n // ── quote_area.type ──────────────────────────────────────────────────\n const quoteArea = card.quote_area as Record<string, unknown> | undefined;\n if (quoteArea && typeof quoteArea === \"object\" && \"type\" in quoteArea) {\n const fixed = coerceToInt(quoteArea.type);\n if (fixed !== undefined && quoteArea.type !== fixed) {\n fixes.push(`quote_area.type: ${JSON.stringify(quoteArea.type)} → ${fixed}`);\n quoteArea.type = fixed;\n }\n }\n\n // ── image_text_area.type ─────────────────────────────────────────────\n const imageTextArea = card.image_text_area as Record<string, unknown> | undefined;\n if (imageTextArea && typeof imageTextArea === \"object\" && \"type\" in imageTextArea) {\n const fixed = coerceToInt(imageTextArea.type);\n if (fixed !== undefined && imageTextArea.type !== fixed) {\n fixes.push(`image_text_area.type: ${JSON.stringify(imageTextArea.type)} → ${fixed}`);\n imageTextArea.type = fixed;\n }\n }\n\n // ── horizontal_content_list[].type ───────────────────────────────────\n if (Array.isArray(card.horizontal_content_list)) {\n for (const item of card.horizontal_content_list as Record<string, unknown>[]) {\n if (item && typeof item === \"object\" && \"type\" in item) {\n const fixed = coerceToInt(item.type);\n if (fixed !== undefined && item.type !== fixed) {\n fixes.push(`horizontal_content_list.type: ${JSON.stringify(item.type)} → ${fixed}`);\n item.type = fixed;\n }\n }\n }\n }\n\n // ── jump_list[].type ─────────────────────────────────────────────────\n if (Array.isArray(card.jump_list)) {\n for (const item of card.jump_list as Record<string, unknown>[]) {\n if (item && typeof item === \"object\" && \"type\" in item) {\n const fixed = coerceToInt(item.type);\n if (fixed !== undefined && item.type !== fixed) {\n fixes.push(`jump_list.type: ${JSON.stringify(item.type)} → ${fixed}`);\n item.type = fixed;\n }\n }\n }\n }\n\n // ── button_list[].style ──────────────────────────────────────────────\n if (Array.isArray(card.button_list)) {\n for (const btn of card.button_list as Record<string, unknown>[]) {\n if (btn && typeof btn === \"object\" && \"style\" in btn) {\n const fixed = coerceToInt(btn.style);\n if (fixed !== undefined && btn.style !== fixed) {\n fixes.push(`button_list.style: ${JSON.stringify(btn.style)} → ${fixed}`);\n btn.style = fixed;\n }\n }\n }\n }\n\n // ── button_selection.disable ─────────────────────────────────────────\n const buttonSelection = card.button_selection as Record<string, unknown> | undefined;\n if (buttonSelection && typeof buttonSelection === \"object\" && \"disable\" in buttonSelection) {\n const fixed = coerceToBool(buttonSelection.disable);\n if (fixed !== undefined && buttonSelection.disable !== fixed) {\n fixes.push(`button_selection.disable: ${JSON.stringify(buttonSelection.disable)} → ${fixed}`);\n buttonSelection.disable = fixed;\n }\n }\n\n // ── select_list[].disable ────────────────────────────────────────────\n if (Array.isArray(card.select_list)) {\n for (const sel of card.select_list as Record<string, unknown>[]) {\n if (sel && typeof sel === \"object\" && \"disable\" in sel) {\n const fixed = coerceToBool(sel.disable);\n if (fixed !== undefined && sel.disable !== fixed) {\n fixes.push(`select_list.disable: ${JSON.stringify(sel.disable)} → ${fixed}`);\n sel.disable = fixed;\n }\n }\n }\n }\n\n if (fixes.length > 0) {\n log?.(`[template-card-parser] normalizeTemplateCardFields: ${fixes.length} fix(es) applied: ${fixes.join(\"; \")}`);\n }\n\n return card;\n}\n\n// ============================================================================\n// 必填字段校验与自动补全\n// ============================================================================\n\n/** task_id 合法字符:数字、字母、_-@ */\nconst TASK_ID_RE = /^[a-zA-Z0-9_\\-@]+$/;\n\n/**\n * 生成一个合法的 task_id。\n * 格式:task_{cardType}_{timestamp}_{随机4位},确保唯一且符合 API 要求。\n */\nfunction generateTaskId(cardType: string): string {\n const rand = Math.random().toString(36).slice(2, 6);\n return `task_${cardType}_${Date.now()}_${rand}`;\n}\n\n/**\n * 校验并补全模板卡片的必填字段。\n *\n * 在 normalizeTemplateCardFields(类型修正)之后调用,确保卡片结构满足企业微信 API 的必填要求。\n *\n * 补全策略:\n * - task_id:所有卡片统一自动补全(交互型 API 必填,通知型插件也需要用于缓存回写)\n * - main_title:除 text_notice 外的 4 种卡片 API 必填,自动补 { title: \"通知\" }\n * text_notice 要求 main_title.title 与 sub_title_text 至少填一个,缺两个时补 sub_title_text\n * - card_action:text_notice / news_notice API 必填,自动补 { type: 1, url: \"https://work.weixin.qq.com\" }\n * - checkbox:vote_interaction API 必填,无法凭空补全,仅记告警\n * - submit_button:vote_interaction / multiple_interaction API 必填,自动补 { text: \"提交\", key: \"submit_default\" }\n * - button_list:button_interaction API 必填,无法凭空补全,仅记告警\n * - select_list:multiple_interaction API 必填,无法凭空补全,仅记告警\n */\nfunction validateAndFixRequiredFields(\n card: Record<string, unknown>,\n log?: (...args: any[]) => void,\n): Record<string, unknown> {\n const cardType = card.card_type as string;\n const fixes: string[] = [];\n const warnings: string[] = [];\n\n // ── task_id(所有卡片:始终确保唯一性) ─────────────────────────────\n // LLM 可能编造时间戳导致重复,因此无论是否提供了 task_id,\n // 都提取语义前缀 + 代码追加真实时间戳和随机后缀来保证唯一。\n const rawTid = (typeof card.task_id === \"string\" && card.task_id.trim()) ? card.task_id.trim() : \"\";\n const rand = Math.random().toString(36).slice(2, 6);\n const ts = Date.now();\n\n let finalTid: string;\n if (rawTid) {\n // 提取 LLM 的语义前缀:去掉尾部的数字串(LLM 编造的假时间戳)\n const prefix = rawTid.replace(/_\\d{8,}$/, \"\").replace(/[^a-zA-Z0-9_\\-@]/g, \"_\").slice(0, 80);\n finalTid = prefix ? `${prefix}_${ts}_${rand}` : `task_${cardType}_${ts}_${rand}`;\n } else {\n finalTid = `task_${cardType}_${ts}_${rand}`;\n }\n\n if (finalTid !== rawTid) {\n fixes.push(`task_id: \"${rawTid || \"(missing)\"}\" → \"${finalTid}\"`);\n }\n card.task_id = finalTid;\n\n // ── main_title ────────────────────────────────────────────────────────\n const mainTitle = card.main_title as Record<string, unknown> | undefined;\n const hasMainTitle = mainTitle && typeof mainTitle === \"object\" &&\n (typeof mainTitle.title === \"string\" && mainTitle.title.trim());\n const hasSubTitleText = typeof card.sub_title_text === \"string\" && (card.sub_title_text as string).trim();\n\n switch (cardType) {\n case \"text_notice\":\n // text_notice: main_title.title 和 sub_title_text 至少一个\n if (!hasMainTitle && !hasSubTitleText) {\n card.sub_title_text = card.sub_title_text || \"通知\";\n fixes.push(`sub_title_text: (missing, no main_title either) → fallback \"通知\"`);\n }\n break;\n\n case \"news_notice\":\n case \"button_interaction\":\n case \"vote_interaction\":\n case \"multiple_interaction\":\n // 这四种 main_title 为必填\n if (!mainTitle || typeof mainTitle !== \"object\") {\n card.main_title = { title: \"通知\" };\n fixes.push(`main_title: (missing) → { title: \"通知\" }`);\n } else if (!hasMainTitle) {\n (mainTitle as Record<string, unknown>).title = \"通知\";\n fixes.push(`main_title.title: (empty) → \"通知\"`);\n }\n break;\n }\n\n // ── card_action(text_notice / news_notice 必填) ──────────────────\n if (cardType === \"text_notice\" || cardType === \"news_notice\") {\n if (!card.card_action || typeof card.card_action !== \"object\") {\n card.card_action = { type: 1, url: \"https://work.weixin.qq.com\" };\n fixes.push(`card_action: (missing) → { type: 1, url: \"https://work.weixin.qq.com\" }`);\n }\n }\n\n // ── submit_button(vote_interaction / multiple_interaction 必填) ──\n if (cardType === \"vote_interaction\" || cardType === \"multiple_interaction\") {\n if (!card.submit_button || typeof card.submit_button !== \"object\") {\n card.submit_button = { text: \"提交\", key: `submit_${cardType}_${Date.now()}` };\n fixes.push(`submit_button: (missing) → auto-generated`);\n }\n }\n\n // ── 核心业务字段(无法凭空补全,仅告警) ────────────────────────────\n if (cardType === \"button_interaction\") {\n if (!Array.isArray(card.button_list) || card.button_list.length === 0) {\n warnings.push(`button_list is missing or empty (required for button_interaction)`);\n }\n }\n\n if (cardType === \"vote_interaction\") {\n if (!card.checkbox || typeof card.checkbox !== \"object\") {\n warnings.push(`checkbox is missing (required for vote_interaction)`);\n }\n }\n\n if (cardType === \"multiple_interaction\") {\n if (!Array.isArray(card.select_list) || card.select_list.length === 0) {\n warnings.push(`select_list is missing or empty (required for multiple_interaction)`);\n }\n }\n\n if (fixes.length > 0) {\n log?.(`[template-card-parser] validateAndFixRequiredFields: ${fixes.length} fix(es): ${fixes.join(\"; \")}`);\n }\n if (warnings.length > 0) {\n log?.(`[template-card-parser] validateAndFixRequiredFields: ${warnings.length} warning(s): ${warnings.join(\"; \")}`);\n }\n\n return card;\n}\n\n// ============================================================================\n// 简化格式 → 企微 API 格式转换(vote_interaction / multiple_interaction)\n// ============================================================================\n\n/**\n * 生成唯一的 question_key / submit_button.key。\n */\nfunction generateKey(prefix: string): string {\n const rand = Math.random().toString(36).slice(2, 6);\n return `${prefix}_${Date.now()}_${rand}`;\n}\n\n/**\n * 将 vote_interaction 的简化格式转换为企微 API 格式。\n *\n * 简化格式字段:\n * title → main_title.title\n * description → main_title.desc\n * options → checkbox.option_list(每个 {id, text} 直接透传)\n * mode → checkbox.mode(0=单选 1=多选)\n * submit_text → submit_button.text\n *\n * 代码自动生成:checkbox.question_key, submit_button.key\n *\n * 如果 LLM 已输出了合法的 API 原始格式(有 checkbox.option_list),则跳过转换直接透传。\n */\nfunction transformVoteInteraction(\n card: Record<string, unknown>,\n log?: (...args: any[]) => void,\n): Record<string, unknown> {\n // 防御性:如果已经是合法 API 格式,跳过\n const existingCheckbox = card.checkbox as Record<string, unknown> | undefined;\n if (existingCheckbox && typeof existingCheckbox === \"object\" && Array.isArray(existingCheckbox.option_list)) {\n log?.(`[template-card-parser] transformVoteInteraction: already has checkbox.option_list, skipping transform`);\n return card;\n }\n\n // 提取 options(简化格式的核心字段)\n const options = card.options as Array<Record<string, unknown>> | undefined;\n if (!Array.isArray(options) || options.length === 0) {\n log?.(`[template-card-parser] transformVoteInteraction: no \"options\" array found, skipping transform`);\n return card;\n }\n\n log?.(`[template-card-parser] transformVoteInteraction: transforming simplified format → API format`);\n log?.(`[template-card-parser] transformVoteInteraction: input=${JSON.stringify(card)}`);\n\n // ── 构建 main_title ──\n const title = card.title as string | undefined;\n const description = card.description as string | undefined;\n if (title || description) {\n card.main_title = {\n ...(title ? { title } : {}),\n ...(description ? { desc: description } : {}),\n };\n delete card.title;\n delete card.description;\n }\n\n // ── 构建 checkbox(最多 20 个选项) ──\n const mode = coerceCheckboxMode(card.mode) ?? 0;\n const questionKey = generateKey(\"vote\");\n const clampedOptions = options.slice(0, 20);\n if (options.length > 20) {\n log?.(`[template-card-parser] transformVoteInteraction: options count ${options.length} exceeds max 20, clamped to 20`);\n }\n card.checkbox = {\n question_key: questionKey,\n mode,\n option_list: clampedOptions.map((opt) => ({\n id: String(opt.id ?? opt.value ?? `opt_${Math.random().toString(36).slice(2, 6)}`),\n text: String(opt.text ?? opt.label ?? opt.name ?? \"\"),\n })),\n };\n delete card.options;\n delete card.mode;\n\n // ── 构建 submit_button ──\n const submitText = (card.submit_text as string) || \"提交\";\n card.submit_button = {\n text: submitText,\n key: generateKey(\"submit_vote\"),\n };\n delete card.submit_text;\n\n // ── 清理 LLM 可能杜撰的无效字段 ──\n delete card.vote_question;\n delete card.vote_option;\n delete card.vote_options;\n\n log?.(`[template-card-parser] transformVoteInteraction: output=${JSON.stringify(card)}`);\n\n return card;\n}\n\n/**\n * 将 multiple_interaction 的简化格式转换为企微 API 格式。\n *\n * 简化格式字段:\n * title → main_title.title\n * description → main_title.desc\n * selectors → select_list(每个 {title, options: [{id, text}]} → {question_key, title, option_list})\n * submit_text → submit_button.text\n *\n * 代码自动生成:select_list[].question_key, submit_button.key\n *\n * 如果 LLM 已输出了合法的 API 原始格式(有 select_list[0].option_list),则跳过转换直接透传。\n */\nfunction transformMultipleInteraction(\n card: Record<string, unknown>,\n log?: (...args: any[]) => void,\n): Record<string, unknown> {\n // 防御性:如果已经是合法 API 格式,跳过\n const existingSelectList = card.select_list as Array<Record<string, unknown>> | undefined;\n if (\n Array.isArray(existingSelectList) &&\n existingSelectList.length > 0 &&\n Array.isArray(existingSelectList[0]?.option_list)\n ) {\n log?.(`[template-card-parser] transformMultipleInteraction: already has select_list[].option_list, skipping transform`);\n return card;\n }\n\n // 提取 selectors(简化格式的核心字段)\n const selectors = card.selectors as Array<Record<string, unknown>> | undefined;\n if (!Array.isArray(selectors) || selectors.length === 0) {\n log?.(`[template-card-parser] transformMultipleInteraction: no \"selectors\" array found, skipping transform`);\n return card;\n }\n\n log?.(`[template-card-parser] transformMultipleInteraction: transforming simplified format → API format`);\n log?.(`[template-card-parser] transformMultipleInteraction: input=${JSON.stringify(card)}`);\n\n // ── 构建 main_title ──\n const title = card.title as string | undefined;\n const description = card.description as string | undefined;\n if (title || description) {\n card.main_title = {\n ...(title ? { title } : {}),\n ...(description ? { desc: description } : {}),\n };\n delete card.title;\n delete card.description;\n }\n\n // ── 构建 select_list(最多 3 个选择器,每个最多 10 个选项) ──\n const clampedSelectors = selectors.slice(0, 3);\n if (selectors.length > 3) {\n log?.(`[template-card-parser] transformMultipleInteraction: selectors count ${selectors.length} exceeds max 3, clamped to 3`);\n }\n card.select_list = clampedSelectors.map((sel, idx) => {\n const selectorOptions = ((sel.options as Array<Record<string, unknown>>) ?? []).slice(0, 10);\n return {\n question_key: generateKey(`sel_${idx}`),\n title: String(sel.title ?? sel.label ?? `选择${idx + 1}`),\n option_list: selectorOptions.map((opt) => ({\n id: String(opt.id ?? opt.value ?? `opt_${Math.random().toString(36).slice(2, 6)}`),\n text: String(opt.text ?? opt.label ?? opt.name ?? \"\"),\n })),\n };\n });\n delete card.selectors;\n\n // ── 构建 submit_button ──\n const submitText = (card.submit_text as string) || \"提交\";\n card.submit_button = {\n text: submitText,\n key: generateKey(\"submit_multi\"),\n };\n delete card.submit_text;\n\n log?.(`[template-card-parser] transformMultipleInteraction: output=${JSON.stringify(card)}`);\n\n return card;\n}\n\n/**\n * 对 vote_interaction / multiple_interaction 执行简化格式转换。\n * 其他 card_type 直接跳过。\n */\nfunction transformSimplifiedCard(\n card: Record<string, unknown>,\n log?: (...args: any[]) => void,\n): Record<string, unknown> {\n const cardType = card.card_type as string;\n if (cardType === \"vote_interaction\") {\n return transformVoteInteraction(card, log);\n }\n if (cardType === \"multiple_interaction\") {\n return transformMultipleInteraction(card, log);\n }\n return card;\n}\n\n/**\n * 匹配 markdown 代码块的正则表达式\n * 支持 ```json ... ``` 和 ``` ... ``` 两种格式\n */\nconst CODE_BLOCK_RE = /```(?:json)?\\s*\\n([\\s\\S]*?)\\n```/g;\n\n/**\n * 匹配已闭合的代码块(含 card_type 关键词,用于中间帧遮罩)\n * 与 CODE_BLOCK_RE 相同,但用于 maskTemplateCardBlocks 中单独实例化\n */\nconst CLOSED_BLOCK_RE = /```(?:json)?\\s*\\n([\\s\\S]*?)\\n```/g;\n\n/**\n * 匹配未闭合的代码块尾部(LLM 正在输出中的代码块)\n * 以 ```json 或 ``` 开头,后面有内容但没有闭合的 ```\n */\nconst UNCLOSED_BLOCK_RE = /```(?:json)?\\s*\\n[\\s\\S]*$/;\n\n/**\n * 从文本中提取模板卡片 JSON 代码块\n *\n * 匹配规则:\n * 1. 匹配所有 ```json ... ``` 或 ``` ... ``` 格式的代码块\n * 2. 尝试 JSON.parse 解析代码块内容\n * 3. 检查解析结果中是否包含合法的 card_type 字段\n * 4. 合法的卡片从原文中移除,不合法的保留\n *\n * @param text - LLM 回复的完整文本\n * @returns 提取结果,包含卡片列表和剩余文本\n */\nexport function extractTemplateCards(text: string, log?: (...args: any[]) => void): TemplateCardExtractionResult {\n const cards: ExtractedTemplateCard[] = [];\n /** 需要从原文中移除的代码块(记录完整匹配内容) */\n const blocksToRemove: string[] = [];\n\n log?.(`[template-card-parser] extractTemplateCards called, textLength=${text.length}`);\n\n let match: RegExpExecArray | null;\n // 重置正则的 lastIndex,确保从头匹配\n CODE_BLOCK_RE.lastIndex = 0;\n\n let blockIndex = 0;\n while ((match = CODE_BLOCK_RE.exec(text)) !== null) {\n const fullMatch = match[0];\n const jsonContent = match[1].trim();\n blockIndex++;\n\n log?.(`[template-card-parser] Found code block #${blockIndex}, length=${fullMatch.length}, preview=${jsonContent.slice(0, 1000)}...`);\n\n // 尝试解析 JSON\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(jsonContent) as Record<string, unknown>;\n } catch (e) {\n // JSON 解析失败,保留在原文中\n log?.(`[template-card-parser] Code block #${blockIndex} JSON parse failed: ${String(e)}`);\n continue;\n }\n\n // 检查是否包含合法的 card_type\n const cardType = parsed.card_type;\n if (typeof cardType !== \"string\" || !VALID_CARD_TYPES.includes(cardType)) {\n // card_type 不合法,保留在原文中\n log?.(`[template-card-parser] Code block #${blockIndex} has invalid card_type=\"${String(cardType)}\", skipping`);\n continue;\n }\n\n log?.(`[template-card-parser] Code block #${blockIndex} is valid template card, card_type=\"${cardType}\"`);\n\n // vote_interaction / multiple_interaction:简化格式 → API 格式转换\n transformSimplifiedCard(parsed, log);\n\n // 修正 LLM 可能输出的错误字段类型(如 checkbox.mode: \"multi\" → 1)\n normalizeTemplateCardFields(parsed, log);\n\n // 校验并补全必填字段(如缺失的 task_id、main_title、card_action)\n validateAndFixRequiredFields(parsed, log);\n\n // 合法的模板卡片,收集并标记移除\n cards.push({\n cardJson: parsed,\n cardType,\n });\n blocksToRemove.push(fullMatch);\n }\n\n // 从原文中移除已提取的代码块,生成剩余文本\n let remainingText = text;\n for (const block of blocksToRemove) {\n remainingText = remainingText.replace(block, \"\");\n }\n // 清理多余空行(连续 3 个以上换行合并为 2 个)\n remainingText = remainingText.replace(/\\n{3,}/g, \"\\n\\n\").trim();\n\n log?.(`[template-card-parser] Extraction done: ${cards.length} card(s) found, remainingTextLength=${remainingText.length}`);\n\n return { cards, remainingText };\n}\n\n/**\n * 遮罩文本中的模板卡片代码块(用于流式中间帧展示)\n *\n * 在 LLM 流式输出过程中,累积文本可能包含:\n * 1. 已闭合的模板卡片 JSON 代码块 → 替换为友好提示文本\n * 2. 正在构建中的未闭合代码块 → 截断隐藏,避免 JSON 源码暴露\n *\n * 此函数仅做文本替换,不做 JSON 解析验证(中间帧性能优先)。\n * 只要代码块内容中出现 \"card_type\" 关键词就认为是模板卡片并遮罩。\n *\n * @param text - 当前累积文本\n * @returns 遮罩后的展示文本\n */\nexport function maskTemplateCardBlocks(text: string, log?: (...args: any[]) => void): string {\n let masked = text;\n let closedMaskCount = 0;\n let unclosedMasked = false;\n\n // 步骤一:处理已闭合的代码块\n CLOSED_BLOCK_RE.lastIndex = 0;\n masked = masked.replace(CLOSED_BLOCK_RE, (fullMatch, content: string) => {\n // 检查代码块内容是否包含 card_type 关键词\n if (/[\"']card_type[\"']/.test(content)) {\n closedMaskCount++;\n return \"\\n\\n📋 *正在生成卡片消息...*\\n\\n\";\n }\n // 非模板卡片代码块,保留原样\n return fullMatch;\n });\n\n // 步骤二:处理未闭合的代码块尾部(LLM 仍在输出中)\n // 检查是否有以 ``` 开头但没有闭合的代码块\n const unclosedMatch = UNCLOSED_BLOCK_RE.exec(masked);\n if (unclosedMatch) {\n const unclosedContent = unclosedMatch[0];\n // 如果未闭合部分包含 card_type 关键词,说明正在构建模板卡片 → 截断\n if (/[\"']card_type[\"']/.test(unclosedContent)) {\n unclosedMasked = true;\n masked = masked.slice(0, unclosedMatch.index) + \"\\n\\n📋 *正在生成卡片消息...*\";\n }\n }\n\n // 有遮罩行为时才打日志,避免每帧都刷屏\n if (closedMaskCount > 0 || unclosedMasked) {\n log?.(`[template-card-parser] maskTemplateCardBlocks: closedMasked=${closedMaskCount}, unclosedMasked=${unclosedMasked}, textLength=${text.length}, maskedLength=${masked.length}`);\n }\n\n return masked;\n}\n"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@
11
11
  * @param message - 超时错误消息
12
12
  * @returns 带超时保护的 Promise
13
13
  */
14
- export function withTimeout(promise, timeoutMs, message) {
14
+ function withTimeout(promise, timeoutMs, message) {
15
15
  if (timeoutMs <= 0 || !Number.isFinite(timeoutMs)) {
16
16
  return promise;
17
17
  }
@@ -28,9 +28,12 @@ export function withTimeout(promise, timeoutMs, message) {
28
28
  /**
29
29
  * 超时错误类型
30
30
  */
31
- export class TimeoutError extends Error {
31
+ class TimeoutError extends Error {
32
32
  constructor(message) {
33
33
  super(message);
34
34
  this.name = "TimeoutError";
35
35
  }
36
36
  }
37
+
38
+ export { TimeoutError, withTimeout };
39
+ //# sourceMappingURL=timeout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeout.js","sources":["../../../../../src/timeout.ts"],"sourcesContent":["/**\n * 超时控制工具模块\n *\n * 为异步操作提供统一的超时保护机制\n */\n\n/**\n * 为 Promise 添加超时保护\n *\n * @param promise - 原始 Promise\n * @param timeoutMs - 超时时间(毫秒)\n * @param message - 超时错误消息\n * @returns 带超时保护的 Promise\n */\nexport function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n message?: string,\n): Promise<T> {\n if (timeoutMs <= 0 || !Number.isFinite(timeoutMs)) {\n return promise;\n }\n\n let timeoutId: ReturnType<typeof setTimeout>;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new TimeoutError(message ?? `Operation timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n });\n\n return Promise.race([promise, timeoutPromise]).finally(() => {\n clearTimeout(timeoutId);\n });\n}\n\n/**\n * 超时错误类型\n */\nexport class TimeoutError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TimeoutError\";\n }\n}\n"],"names":[],"mappings":"AAAA;;;;AAIG;AAEH;;;;;;;AAOG;SACa,WAAW,CACzB,OAAmB,EACnB,SAAiB,EACjB,OAAgB,EAAA;AAEhB,IAAA,IAAI,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;AACjD,QAAA,OAAO,OAAO;IAChB;AAEA,IAAA,IAAI,SAAwC;IAE5C,MAAM,cAAc,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,KAAI;AACtD,QAAA,SAAS,GAAG,UAAU,CAAC,MAAK;YAC1B,MAAM,CAAC,IAAI,YAAY,CAAC,OAAO,IAAI,CAAA,0BAAA,EAA6B,SAAS,CAAA,EAAA,CAAI,CAAC,CAAC;QACjF,CAAC,EAAE,SAAS,CAAC;AACf,IAAA,CAAC,CAAC;AAEF,IAAA,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,MAAK;QAC1D,YAAY,CAAC,SAAS,CAAC;AACzB,IAAA,CAAC,CAAC;AACJ;AAEA;;AAEG;AACG,MAAO,YAAa,SAAQ,KAAK,CAAA;AACrC,IAAA,WAAA,CAAY,OAAe,EAAA;QACzB,KAAK,CAAC,OAAO,CAAC;AACd,QAAA,IAAI,CAAC,IAAI,GAAG,cAAc;IAC5B;AACD;;;;"}