@wecom/wecom-openclaw-plugin 2026.4.2 → 2026.4.3-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +395 -10
- package/dist/index.d.ts +2 -4
- package/dist/index.js +58 -0
- package/dist/src/accounts.d.ts +57 -0
- package/dist/src/accounts.js +222 -0
- package/dist/src/agent/api-client.d.ts +94 -0
- package/dist/src/agent/api-client.js +292 -0
- package/dist/src/agent/handler.d.ts +63 -0
- package/dist/src/agent/handler.js +551 -0
- package/dist/src/agent/index.d.ts +5 -0
- package/dist/src/agent/index.js +5 -0
- package/dist/src/agent/webhook.d.ts +24 -0
- package/dist/src/agent/webhook.js +228 -0
- package/dist/src/agent/xml.d.ts +21 -0
- package/dist/src/agent/xml.js +41 -0
- package/dist/src/channel.js +579 -0
- package/dist/src/chat-queue.d.ts +31 -0
- package/dist/src/chat-queue.js +45 -0
- package/dist/{esm/types/src → src}/const.d.ts +46 -0
- package/dist/{esm/src → src}/const.js +77 -30
- package/dist/{esm/src → src}/dm-policy.js +6 -9
- package/dist/src/dynamic-agent.d.ts +37 -0
- package/dist/src/dynamic-agent.js +60 -0
- package/dist/src/dynamic-routing.d.ts +65 -0
- package/dist/src/dynamic-routing.js +58 -0
- package/dist/{esm/src → src}/group-policy.js +8 -22
- package/dist/src/http.d.ts +26 -0
- package/dist/src/http.js +93 -0
- package/dist/{esm/types/src → src}/interface.d.ts +0 -2
- package/dist/src/interface.js +4 -0
- package/dist/src/mcp/index.js +6 -0
- package/dist/{esm/src → src}/mcp/interceptors/biz-error.js +2 -6
- package/dist/{esm/src → src}/mcp/interceptors/index.js +6 -10
- package/dist/{esm/src → src}/mcp/interceptors/msg-media.js +6 -10
- package/dist/{esm/src → src}/mcp/interceptors/smartpage-create.js +2 -6
- package/dist/{esm/src → src}/mcp/interceptors/smartpage-export.js +2 -6
- package/dist/src/mcp/interceptors/types.js +7 -0
- package/dist/{esm/src → src}/mcp/schema.js +1 -4
- package/dist/{esm/src → src}/mcp/tool.js +4 -8
- package/dist/{esm/src → src}/mcp/transport.js +15 -16
- package/dist/{esm/src → src}/media-handler.js +6 -10
- package/dist/{esm/src → src}/media-uploader.js +64 -10
- package/dist/{esm/src → src}/message-parser.js +1 -4
- package/dist/{esm/types/src → src}/message-sender.d.ts +18 -0
- package/dist/{esm/src → src}/message-sender.js +45 -11
- package/dist/{esm/src → src}/monitor.js +145 -292
- package/dist/{esm/src → src}/onboarding.js +22 -26
- package/dist/{esm/types/src → src}/openclaw-compat.d.ts +49 -0
- package/dist/{esm/src → src}/openclaw-compat.js +133 -27
- package/dist/{esm/src → src}/reqid-store.js +4 -7
- package/dist/{esm/src → src}/runtime.js +2 -5
- package/dist/src/shared/command-auth.d.ts +23 -0
- package/dist/src/shared/command-auth.js +73 -0
- package/dist/src/shared/xml-parser.d.ts +46 -0
- package/dist/src/shared/xml-parser.js +210 -0
- package/dist/{esm/src → src}/state-dir-resolve.js +3 -7
- package/dist/{esm/src → src}/state-manager.js +84 -16
- package/dist/src/target.d.ts +35 -0
- package/dist/src/target.js +67 -0
- package/dist/src/template-card-manager.d.ts +55 -0
- package/dist/src/template-card-manager.js +205 -0
- package/dist/{esm/src → src}/template-card-parser.js +17 -8
- package/dist/{esm/src → src}/timeout.js +2 -5
- package/dist/src/types/account.d.ts +29 -0
- package/dist/src/types/account.js +4 -0
- package/dist/src/types/config.d.ts +91 -0
- package/dist/src/types/config.js +7 -0
- package/dist/src/types/constants.d.ts +42 -0
- package/dist/src/types/constants.js +42 -0
- package/dist/src/types/index.d.ts +7 -0
- package/dist/src/types/index.js +5 -0
- package/dist/src/types/message.d.ts +238 -0
- package/dist/src/types/message.js +5 -0
- package/dist/{esm/types/src → src}/utils.d.ts +41 -0
- package/dist/{esm/src → src}/utils.js +24 -10
- package/dist/src/version.d.ts +2 -0
- package/dist/src/version.js +24 -0
- package/dist/src/webhook/command-auth.d.ts +47 -0
- package/dist/src/webhook/command-auth.js +96 -0
- package/dist/src/webhook/gateway.d.ts +36 -0
- package/dist/src/webhook/gateway.js +235 -0
- package/dist/src/webhook/handler.d.ts +18 -0
- package/dist/src/webhook/handler.js +420 -0
- package/dist/src/webhook/helpers.d.ts +156 -0
- package/dist/src/webhook/helpers.js +833 -0
- package/dist/src/webhook/http.d.ts +26 -0
- package/dist/src/webhook/http.js +94 -0
- package/dist/src/webhook/index.d.ts +11 -0
- package/dist/src/webhook/index.js +14 -0
- package/dist/src/webhook/media.d.ts +29 -0
- package/dist/src/webhook/media.js +88 -0
- package/dist/src/webhook/monitor.d.ts +59 -0
- package/dist/src/webhook/monitor.js +1161 -0
- package/dist/src/webhook/state.d.ts +220 -0
- package/dist/src/webhook/state.js +491 -0
- package/dist/src/webhook/target.d.ts +41 -0
- package/dist/src/webhook/target.js +132 -0
- package/dist/src/webhook/types.d.ts +347 -0
- package/dist/src/webhook/types.js +33 -0
- package/dist/src/webhook/video-frame.d.ts +13 -0
- package/dist/src/webhook/video-frame.js +30 -0
- package/package.json +15 -7
- package/dist/cjs/index.js +0 -45
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/src/channel.js +0 -339
- package/dist/cjs/src/channel.js.map +0 -1
- package/dist/cjs/src/const.js +0 -137
- package/dist/cjs/src/const.js.map +0 -1
- package/dist/cjs/src/dm-policy.js +0 -89
- package/dist/cjs/src/dm-policy.js.map +0 -1
- package/dist/cjs/src/group-policy.js +0 -133
- package/dist/cjs/src/group-policy.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/biz-error.js +0 -70
- package/dist/cjs/src/mcp/interceptors/biz-error.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/index.js +0 -73
- package/dist/cjs/src/mcp/interceptors/index.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/msg-media.js +0 -159
- package/dist/cjs/src/mcp/interceptors/msg-media.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/smartpage-create.js +0 -157
- package/dist/cjs/src/mcp/interceptors/smartpage-create.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/smartpage-export.js +0 -93
- package/dist/cjs/src/mcp/interceptors/smartpage-export.js.map +0 -1
- package/dist/cjs/src/mcp/schema.js +0 -104
- package/dist/cjs/src/mcp/schema.js.map +0 -1
- package/dist/cjs/src/mcp/tool.js +0 -208
- package/dist/cjs/src/mcp/tool.js.map +0 -1
- package/dist/cjs/src/mcp/transport.js +0 -417
- package/dist/cjs/src/mcp/transport.js.map +0 -1
- package/dist/cjs/src/media-handler.js +0 -129
- package/dist/cjs/src/media-handler.js.map +0 -1
- package/dist/cjs/src/media-uploader.js +0 -284
- package/dist/cjs/src/media-uploader.js.map +0 -1
- package/dist/cjs/src/message-parser.js +0 -134
- package/dist/cjs/src/message-parser.js.map +0 -1
- package/dist/cjs/src/message-sender.js +0 -89
- package/dist/cjs/src/message-sender.js.map +0 -1
- package/dist/cjs/src/monitor.js +0 -991
- package/dist/cjs/src/monitor.js.map +0 -1
- package/dist/cjs/src/onboarding.js +0 -182
- package/dist/cjs/src/onboarding.js.map +0 -1
- package/dist/cjs/src/openclaw-compat.js +0 -400
- package/dist/cjs/src/openclaw-compat.js.map +0 -1
- package/dist/cjs/src/reqid-store.js +0 -88
- package/dist/cjs/src/reqid-store.js.map +0 -1
- package/dist/cjs/src/runtime.js +0 -16
- package/dist/cjs/src/runtime.js.map +0 -1
- package/dist/cjs/src/state-dir-resolve.js +0 -35
- package/dist/cjs/src/state-dir-resolve.js.map +0 -1
- package/dist/cjs/src/state-manager.js +0 -158
- package/dist/cjs/src/state-manager.js.map +0 -1
- package/dist/cjs/src/template-card-parser.js +0 -641
- package/dist/cjs/src/template-card-parser.js.map +0 -1
- package/dist/cjs/src/timeout.js +0 -42
- package/dist/cjs/src/timeout.js.map +0 -1
- package/dist/cjs/src/utils.js +0 -60
- package/dist/cjs/src/utils.js.map +0 -1
- package/dist/cjs/src/version.js +0 -26
- package/dist/cjs/src/version.js.map +0 -1
- package/dist/esm/index.js +0 -41
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/src/channel.js +0 -337
- package/dist/esm/src/channel.js.map +0 -1
- package/dist/esm/src/const.js.map +0 -1
- package/dist/esm/src/dm-policy.js.map +0 -1
- package/dist/esm/src/group-policy.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/biz-error.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/index.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/msg-media.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/smartpage-create.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/smartpage-export.js.map +0 -1
- package/dist/esm/src/mcp/schema.js.map +0 -1
- package/dist/esm/src/mcp/tool.js.map +0 -1
- package/dist/esm/src/mcp/transport.js.map +0 -1
- package/dist/esm/src/media-handler.js.map +0 -1
- package/dist/esm/src/media-uploader.js.map +0 -1
- package/dist/esm/src/message-parser.js.map +0 -1
- package/dist/esm/src/message-sender.js.map +0 -1
- package/dist/esm/src/monitor.js.map +0 -1
- package/dist/esm/src/onboarding.js.map +0 -1
- package/dist/esm/src/openclaw-compat.js.map +0 -1
- package/dist/esm/src/reqid-store.js.map +0 -1
- package/dist/esm/src/runtime.js.map +0 -1
- package/dist/esm/src/state-dir-resolve.js.map +0 -1
- package/dist/esm/src/state-manager.js.map +0 -1
- package/dist/esm/src/template-card-parser.js.map +0 -1
- package/dist/esm/src/timeout.js.map +0 -1
- package/dist/esm/src/utils.js.map +0 -1
- package/dist/esm/src/version.js +0 -23
- package/dist/esm/src/version.js.map +0 -1
- package/dist/esm/types/index.d.ts +0 -9
- package/dist/esm/types/src/version.d.ts +0 -2
- /package/dist/{esm/types/src → src}/channel.d.ts +0 -0
- /package/dist/{esm/types/src → src}/dm-policy.d.ts +0 -0
- /package/dist/{esm/types/src → src}/group-policy.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/index.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/biz-error.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/index.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/msg-media.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/smartpage-create.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/smartpage-export.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/types.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/schema.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/tool.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/transport.d.ts +0 -0
- /package/dist/{esm/types/src → src}/media-handler.d.ts +0 -0
- /package/dist/{esm/types/src → src}/media-uploader.d.ts +0 -0
- /package/dist/{esm/types/src → src}/message-parser.d.ts +0 -0
- /package/dist/{esm/types/src → src}/monitor.d.ts +0 -0
- /package/dist/{esm/types/src → src}/onboarding.d.ts +0 -0
- /package/dist/{esm/types/src → src}/reqid-store.d.ts +0 -0
- /package/dist/{esm/types/src → src}/runtime.d.ts +0 -0
- /package/dist/{esm/types/src → src}/state-dir-resolve.d.ts +0 -0
- /package/dist/{esm/types/src → src}/state-manager.d.ts +0 -0
- /package/dist/{esm/types/src → src}/template-card-parser.d.ts +0 -0
- /package/dist/{esm/types/src → src}/timeout.d.ts +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"transport.js","sources":["../../../../../../src/mcp/transport.ts"],"sourcesContent":["/**\n * MCP Streamable HTTP 传输层模块\n *\n * 负责:\n * - MCP JSON-RPC over HTTP 通信(发送请求、解析响应)\n * - Streamable HTTP session 生命周期管理(initialize 握手 → Mcp-Session-Id 维护 → 失效重建)\n * - 自动检测无状态 Server:如果 initialize 响应未返回 Mcp-Session-Id,\n * 则标记为无状态模式,后续请求跳过握手和 session 管理\n * - SSE 流式响应解析\n * - MCP 配置运行时缓存(通过 WSClient 拉取 URL 并缓存在内存中)\n */\n\nimport { generateReqId } from \"@wecom/aibot-node-sdk\";\nimport { DEFAULT_ACCOUNT_ID } from \"../openclaw-compat.js\";\nimport { getWeComWebSocket } from \"../state-manager.js\";\nimport { MCP_GET_CONFIG_CMD, MCP_CONFIG_FETCH_TIMEOUT_MS } from \"../const.js\";\nimport { withTimeout } from \"../timeout.js\";\nimport { PLUGIN_VERSION } from \"../version.js\";\n\n// ============================================================================\n// 类型定义\n// ============================================================================\n\n/** MCP JSON-RPC 请求体 */\ninterface JsonRpcRequest {\n jsonrpc: \"2.0\";\n id?: string;\n method: string;\n params?: Record<string, unknown>;\n}\n\n/** MCP JSON-RPC 响应体 */\ninterface JsonRpcResponse {\n jsonrpc: \"2.0\";\n id: number | string;\n result?: unknown;\n error?: {\n code: number;\n message: string;\n data?: unknown;\n };\n}\n\n/**\n * Streamable HTTP 会话信息\n *\n * 每个 MCP Server category 维护一个独立的会话,包含:\n * - sessionId: 服务端通过 Mcp-Session-Id 响应头返回的会话标识\n * - initialized: 是否已完成 initialize 握手\n * - stateless: 服务端未返回 Mcp-Session-Id 时标记为无状态模式,后续请求跳过 session 管理\n */\ninterface McpSession {\n sessionId: string | null;\n initialized: boolean;\n stateless: boolean;\n}\n\n// ============================================================================\n// 内部状态\n// ============================================================================\n\n/** HTTP 请求超时时间(毫秒) */\nconst HTTP_REQUEST_TIMEOUT_MS = 30_000;\n\n/** 媒体下载请求超时时间(毫秒),base64 编码的媒体文件最大可达 ~27MB */\nexport const MEDIA_DOWNLOAD_TIMEOUT_MS = 120_000;\n\n/** 日志前缀 */\nconst LOG_TAG = \"[mcp]\";\n\n/**\n * MCP JSON-RPC 错误\n *\n * 携带服务端返回的 JSON-RPC error.code,\n * 用于上层按错误码进行差异化处理(如特定错误码触发缓存清理)。\n */\nexport class McpRpcError extends Error {\n constructor(\n public readonly code: number,\n message: string,\n public readonly data?: unknown,\n ) {\n super(message);\n this.name = \"McpRpcError\";\n }\n}\n\n/**\n * MCP HTTP 错误\n *\n * 携带 HTTP 状态码,用于精确判断 session 失效(404)等场景,\n * 避免通过字符串匹配 \"404\" 导致的误判。\n */\nexport class McpHttpError extends Error {\n constructor(\n public readonly statusCode: number,\n message: string,\n ) {\n super(message);\n this.name = \"McpHttpError\";\n }\n}\n\n/**\n * 需要清理缓存的 JSON-RPC 错误码集合\n *\n * 当 MCP Server 返回以下错误码时,说明服务端状态已发生变化(如配置变更、\n * 服务重启等),需要清理对应 category 的全部缓存,确保下次请求重新\n * 拉取配置并重建会话。\n *\n * - -32001: 服务不可用(Server Unavailable)\n * - -32002: 配置已变更(Config Changed)\n * - -32003: 认证失败(Auth Failed)\n */\nconst CACHE_CLEAR_ERROR_CODES = new Set([-32001, -32002, -32003]);\n\n/** MCP 配置缓存:category → response.body(完整配置) */\nconst mcpConfigCache = new Map<string, Record<string, unknown>>();\n\n/** Streamable HTTP 会话缓存:category → session */\nconst mcpSessionCache = new Map<string, McpSession>();\n\n/** 已确认为无状态的 MCP Server 品类集合(跳过后续握手) */\nconst statelessCategories = new Set<string>();\n\n/** 正在进行中的 initialize 请求(防止并发重复初始化),key 为 category */\nconst inflightInitRequests = new Map<string, Promise<McpSession>>();\n\n// ============================================================================\n// MCP 配置拉取与缓存\n// ============================================================================\n\n/**\n * 通过 WSClient 拉取指定 category 的 MCP 完整配置\n *\n * @param category - MCP 品类名称,如 doc、contact\n * @returns 完整的 response.body 配置对象(至少包含 url 字段)\n */\nasync function fetchMcpConfig(category: string): Promise<Record<string, unknown>> {\n const wsClient = getWeComWebSocket(DEFAULT_ACCOUNT_ID);\n if (!wsClient) {\n throw new Error(\"WSClient 未连接,无法拉取 MCP 配置\");\n }\n\n const reqId = generateReqId(\"mcp_config\");\n\n const response = await withTimeout(\n wsClient.reply(\n { headers: { req_id: reqId } },\n { biz_type: category, plugin_version: PLUGIN_VERSION },\n MCP_GET_CONFIG_CMD,\n ),\n MCP_CONFIG_FETCH_TIMEOUT_MS,\n `MCP config fetch for \"${category}\" timed out after ${MCP_CONFIG_FETCH_TIMEOUT_MS}ms`,\n );\n\n if (response.errcode !== undefined && response.errcode !== 0) {\n const errMsg = `MCP 配置请求失败: errcode=${response.errcode}, errmsg=${response.errmsg ?? \"unknown\"}`;\n console.error(`${LOG_TAG} ${errMsg}`);\n throw new Error(errMsg);\n }\n\n const body = response.body as { url?: string } | undefined;\n if (!body?.url) {\n throw new Error(\n `MCP 配置响应缺少 url 字段 (category=\"${category}\")`,\n );\n }\n\n console.log(`${LOG_TAG} 配置拉取成功 (category=\"${category}\")`);\n return body as Record<string, unknown>;\n}\n\n/**\n * 获取指定品类的 MCP Server URL\n *\n * 优先从内存缓存中读取,未命中时通过 WSClient 拉取并缓存。\n *\n * @param category - MCP 品类名称\n * @returns MCP Server URL\n */\nasync function getMcpUrl(category: string): Promise<string> {\n // 查内存缓存\n const cached = mcpConfigCache.get(category);\n if (cached) return cached.url as string;\n\n // 缓存未命中,通过 WSClient 拉取\n const body = await fetchMcpConfig(category);\n\n // 写入缓存\n mcpConfigCache.set(category, body);\n\n console.log(`${LOG_TAG} getMcpUrl ${category}: ${body.url}`);\n\n return body.url as string;\n}\n\n// ============================================================================\n// HTTP 底层通信\n// ============================================================================\n\n/**\n * 发送原始 HTTP 请求到 MCP Server(底层方法)\n *\n * 自动携带 Mcp-Session-Id 请求头(如果有),\n * 并从响应头中更新 sessionId。\n */\nasync function sendRawJsonRpc(\n url: string,\n session: McpSession,\n body: JsonRpcRequest,\n timeoutMs: number = HTTP_REQUEST_TIMEOUT_MS,\n): Promise<{ response: Response; rpcResult: unknown; newSessionId: string | null }> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json, text/event-stream\",\n };\n // Streamable HTTP:携带会话 ID\n if (session.sessionId) {\n headers[\"Mcp-Session-Id\"] = session.sessionId;\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new Error(`MCP 请求超时 (${timeoutMs}ms)`);\n }\n throw new Error(`MCP 网络请求失败: ${err instanceof Error ? err.message : String(err)}`);\n } finally {\n clearTimeout(timeoutId);\n }\n\n // 从响应头提取新的 sessionId(不直接修改入参,由调用方决定如何更新)\n const newSessionId = response.headers.get(\"mcp-session-id\");\n\n if (!response.ok) {\n throw new McpHttpError(\n response.status,\n `MCP HTTP 请求失败: ${response.status} ${response.statusText}`,\n );\n }\n\n // Streamable HTTP:notification 响应可能无响应体(204 或 content-length: 0)\n const contentLength = response.headers.get(\"content-length\");\n if (response.status === 204 || contentLength === \"0\") {\n return { response, rpcResult: undefined, newSessionId };\n }\n\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n\n // 处理 SSE 流式响应\n if (contentType.includes(\"text/event-stream\")) {\n return { response, rpcResult: await parseSseResponse(response), newSessionId };\n }\n\n // 普通 JSON 响应 — 先读取文本,防止空内容导致 JSON.parse 报错\n const text = await response.text();\n if (!text.trim()) {\n return { response, rpcResult: undefined, newSessionId };\n }\n\n const rpc = JSON.parse(text) as JsonRpcResponse;\n if (rpc.error) {\n throw new McpRpcError(\n rpc.error.code,\n `MCP 调用错误 [${rpc.error.code}]: ${rpc.error.message}`,\n rpc.error.data,\n );\n }\n return { response, rpcResult: rpc.result, newSessionId };\n}\n\n// ============================================================================\n// Session 管理\n// ============================================================================\n\n/**\n * 对指定 URL 执行 Streamable HTTP 的 initialize 握手\n *\n * 发送 initialize → 接收 serverInfo → 发送 initialized 通知。\n * 如果服务端未返回 Mcp-Session-Id,则标记为无状态模式,后续请求跳过 session 管理。\n */\nasync function initializeSession(url: string, category: string): Promise<McpSession> {\n const session: McpSession = { sessionId: null, initialized: false, stateless: false };\n\n console.log(`${LOG_TAG} 开始 initialize 握手 (category=\"${category}\")`);\n\n // 1. 发送 initialize 请求\n const initBody: JsonRpcRequest = {\n jsonrpc: \"2.0\",\n id: generateReqId(\"mcp_init\"),\n method: \"initialize\",\n params: {\n protocolVersion: \"2025-03-26\",\n capabilities: {},\n clientInfo: { name: \"wecom_mcp\", version: \"1.0.0\" },\n },\n };\n\n const { newSessionId: initSessionId } = await sendRawJsonRpc(url, session, initBody);\n\n // 用返回的 newSessionId 更新 session(不再依赖副作用修改)\n if (initSessionId) {\n session.sessionId = initSessionId;\n }\n\n // 检查服务端是否返回了 Mcp-Session-Id\n // 如果没有返回,说明该 Server 是无状态实现,无需维护 session\n if (!session.sessionId) {\n session.stateless = true;\n session.initialized = true;\n statelessCategories.add(category);\n mcpSessionCache.set(category, session);\n console.log(`${LOG_TAG} 无状态 Server 确认 (category=\"${category}\")`);\n return session;\n }\n\n // 2. 发送 initialized 通知(JSON-RPC notification 不带 id 字段)\n const notifyBody: JsonRpcRequest = {\n jsonrpc: \"2.0\",\n method: \"notifications/initialized\",\n };\n // initialized 通知不需要等待响应,但 Streamable HTTP 要求通过 POST 发送\n const { newSessionId: notifySessionId } = await sendRawJsonRpc(url, session, notifyBody);\n\n // 如果 initialized 通知的响应也携带了 sessionId,以最新的为准\n if (notifySessionId) {\n session.sessionId = notifySessionId;\n }\n\n session.initialized = true;\n mcpSessionCache.set(category, session);\n console.log(`${LOG_TAG} 有状态 Session 建立成功 (category=\"${category}\", sessionId=\"${session.sessionId}\")`);\n return session;\n}\n\n/**\n * 获取或创建指定 URL 的 MCP 会话\n *\n * - 已确认无状态的 category:直接返回空 session,跳过握手\n * - 已有可用有状态会话:直接返回缓存\n * - 其他情况:执行 initialize 握手,并发请求会被合并\n */\nasync function getOrCreateSession(url: string, category: string): Promise<McpSession> {\n // 已确认为无状态的 Server,直接返回空 session 跳过握手\n if (statelessCategories.has(category)) {\n const cached = mcpSessionCache.get(category);\n if (cached) return cached;\n // 首次发现被清除(理论上不会走到这里),重新走握手探测\n }\n\n const cached = mcpSessionCache.get(category);\n if (cached?.initialized) return cached;\n\n // 防止并发重复初始化\n const inflight = inflightInitRequests.get(category);\n if (inflight) return inflight;\n\n const promise = initializeSession(url, category).finally(() => {\n inflightInitRequests.delete(category);\n });\n inflightInitRequests.set(category, promise);\n return promise;\n}\n\n// ============================================================================\n// SSE 解析\n// ============================================================================\n\n/**\n * 解析 SSE 流式响应,提取最终的 JSON-RPC result\n *\n * 按照 SSE 规范,同一事件中的多个 `data:` 行会用换行符拼接。\n * 空行分隔不同事件,取最后一个完整事件的数据。\n */\nasync function parseSseResponse(response: Response): Promise<unknown> {\n const text = await response.text();\n const lines = text.split(\"\\n\");\n\n // 按 SSE 规范解析:空行分隔事件,同一事件内的 data 行用换行拼接\n let currentDataParts: string[] = [];\n let lastEventData = \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"data: \")) {\n currentDataParts.push(line.slice(6));\n } else if (line.startsWith(\"data:\")) {\n // data: 后无空格时,值为空字符串\n currentDataParts.push(line.slice(5));\n } else if (line.trim() === \"\" && currentDataParts.length > 0) {\n // 空行表示事件结束,拼接所有 data 行\n lastEventData = currentDataParts.join(\"\\n\").trim();\n currentDataParts = [];\n }\n }\n\n // 处理最后一个未以空行结尾的事件\n if (currentDataParts.length > 0) {\n lastEventData = currentDataParts.join(\"\\n\").trim();\n }\n\n if (!lastEventData) {\n throw new Error(\"SSE 响应中未包含有效数据\");\n }\n\n try {\n const rpc = JSON.parse(lastEventData) as JsonRpcResponse;\n if (rpc.error) {\n throw new McpRpcError(\n rpc.error.code,\n `MCP 调用错误 [${rpc.error.code}]: ${rpc.error.message}`,\n rpc.error.data,\n );\n }\n return rpc.result;\n } catch (err) {\n if (err instanceof SyntaxError) {\n throw new Error(`SSE 响应解析失败: ${lastEventData.slice(0, 200)}`);\n }\n throw err;\n }\n}\n\n// ============================================================================\n// 公共 API\n// ============================================================================\n\n/**\n * 清理指定品类的所有 MCP 缓存(配置、会话、无状态标记)\n *\n * 当 MCP Server 返回特定错误码时调用,确保下次请求重新拉取配置并重建会话。\n *\n * @param category - MCP 品类名称\n */\nexport function clearCategoryCache(category: string): void {\n console.log(`${LOG_TAG} 清理缓存 (category=\"${category}\")`);\n mcpConfigCache.delete(category);\n mcpSessionCache.delete(category);\n statelessCategories.delete(category);\n inflightInitRequests.delete(category);\n}\n\n/** tools/list 返回的工具描述 */\nexport interface McpToolInfo {\n name: string;\n description?: string;\n inputSchema?: Record<string, unknown>;\n}\n\n/** sendJsonRpc 的可选配置 */\nexport interface SendJsonRpcOptions {\n /** 自定义 HTTP 请求超时时间(毫秒),默认使用 HTTP_REQUEST_TIMEOUT_MS */\n timeoutMs?: number;\n}\n\n/**\n * 发送 JSON-RPC 请求到 MCP Server(Streamable HTTP 协议)\n *\n * 自动管理 session 生命周期:\n * - 无状态 Server:跳过 session 管理,直接发送请求\n * - 有状态 Server:首次调用先执行 initialize 握手,session 失效(404)时自动重建并重试\n *\n * @param category - MCP 品类名称\n * @param method - JSON-RPC 方法名\n * @param params - JSON-RPC 参数\n * @param options - 可选配置(如自定义超时)\n * @returns JSON-RPC result\n */\nexport async function sendJsonRpc(\n category: string,\n method: string,\n params?: Record<string, unknown>,\n options?: SendJsonRpcOptions,\n): Promise<unknown> {\n const url = await getMcpUrl(category);\n const timeoutMs = options?.timeoutMs;\n\n const body: JsonRpcRequest = {\n jsonrpc: \"2.0\",\n id: generateReqId(\"mcp_rpc\"),\n method,\n ...(params !== undefined ? { params } : {}),\n };\n\n let session = await getOrCreateSession(url, category);\n\n try {\n const { rpcResult, newSessionId } = await sendRawJsonRpc(url, session, body, timeoutMs);\n // 用最新的 sessionId 更新 session\n if (newSessionId) {\n session.sessionId = newSessionId;\n }\n return rpcResult;\n } catch (err) {\n // 特定 JSON-RPC 错误码触发缓存清理(统一在传输层处理,上层无需关心)\n if (err instanceof McpRpcError && CACHE_CLEAR_ERROR_CODES.has(err.code)) {\n clearCategoryCache(category);\n }\n\n // 无状态 Server 不存在 session 失效问题,直接抛出错误\n if (session.stateless) throw err;\n\n // 有状态 Server:session 失效时服务端返回 404,需要重新初始化并重试一次\n // 使用 McpHttpError.statusCode 精确匹配,避免字符串匹配 \"404\" 导致误判\n if (err instanceof McpHttpError && err.statusCode === 404) {\n console.log(`${LOG_TAG} Session 失效 (category=\"${category}\"),开始重建...`);\n mcpSessionCache.delete(category);\n\n // 使用 rebuildSession 合并并发的 session 重建请求,避免竞态条件\n session = await rebuildSession(url, category);\n const { rpcResult, newSessionId } = await sendRawJsonRpc(url, session, body, timeoutMs);\n if (newSessionId) {\n session.sessionId = newSessionId;\n }\n return rpcResult;\n }\n\n // 其他错误记录日志后抛出\n console.error(`${LOG_TAG} RPC 请求失败 (category=\"${category}\", method=\"${method}\"): ${err instanceof Error ? err.message : String(err)}`);\n throw err;\n }\n}\n\n/**\n * 合并并发的 session 重建请求\n *\n * 与 getOrCreateSession 类似,使用 inflightInitRequests 防止\n * 多个并发请求同时遇到 404 时重复执行 initialize 握手。\n */\nasync function rebuildSession(url: string, category: string): Promise<McpSession> {\n const inflight = inflightInitRequests.get(category);\n if (inflight) return inflight;\n\n const promise = initializeSession(url, category).finally(() => {\n inflightInitRequests.delete(category);\n });\n inflightInitRequests.set(category, promise);\n return promise;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"media-handler.js","sources":["../../../../../src/media-handler.ts"],"sourcesContent":["/**\n * 企业微信媒体(图片)下载和保存模块\n *\n * 负责下载、检测格式、保存图片到本地,包含超时保护\n */\n\nimport type { OpenClawConfig } from \"openclaw/plugin-sdk/core\";\nimport type { RuntimeEnv } from \"openclaw/plugin-sdk/runtime-env\";\nimport type { WSClient } from \"@wecom/aibot-node-sdk\";\nimport { fileTypeFromBuffer } from \"file-type\";\nimport { getWeComRuntime } from \"./runtime.js\";\nimport { IMAGE_DOWNLOAD_TIMEOUT_MS, FILE_DOWNLOAD_TIMEOUT_MS, DEFAULT_MEDIA_MAX_MB } from \"./const.js\";\nimport { withTimeout } from \"./timeout.js\";\nimport type { ResolvedWeComAccount } from \"./utils.js\";\n\n// ============================================================================\n// 图片格式检测辅助函数(基于 file-type 包)\n// ============================================================================\n\n/**\n * 检查 Buffer 是否为有效的图片格式\n */\nasync function isImageBuffer(data: Buffer): Promise<boolean> {\n const type = await fileTypeFromBuffer(data);\n return type?.mime.startsWith(\"image/\") ?? false;\n}\n\n/**\n * 检测 Buffer 的图片内容类型\n */\nasync function detectImageContentType(data: Buffer): Promise<string> {\n const type = await fileTypeFromBuffer(data);\n if (type?.mime.startsWith(\"image/\")) {\n return type.mime;\n }\n return \"application/octet-stream\";\n}\n\n// ============================================================================\n// 图片下载和保存\n// ============================================================================\n\n/**\n * 下载并保存所有图片到本地,每张图片的下载带超时保护\n */\nexport async function downloadAndSaveImages(params: {\n imageUrls: string[];\n imageAesKeys?: Map<string, string>;\n account: ResolvedWeComAccount;\n config: OpenClawConfig;\n runtime: RuntimeEnv;\n wsClient: WSClient;\n}): Promise<Array<{ path: string; contentType?: string }>> {\n const { imageUrls, config, runtime, wsClient } = params;\n const core = getWeComRuntime();\n const mediaList: Array<{ path: string; contentType?: string }> = [];\n\n for (const imageUrl of imageUrls) {\n try {\n runtime.log?.(`[wecom] Downloading image: url=${imageUrl}`);\n const mediaMaxMb = config.agents?.defaults?.mediaMaxMb ?? DEFAULT_MEDIA_MAX_MB;\n const maxBytes = mediaMaxMb * 1024 * 1024;\n\n let imageBuffer: Buffer;\n let imageContentType: string;\n let originalFilename: string | undefined;\n const imageAesKey = params.imageAesKeys?.get(imageUrl);\n\n try {\n // 优先使用 SDK 的 downloadFile 方法下载(带超时保护)\n const result = await withTimeout(\n wsClient.downloadFile(imageUrl, imageAesKey),\n IMAGE_DOWNLOAD_TIMEOUT_MS,\n `Image download timed out: ${imageUrl}`,\n );\n imageBuffer = result.buffer;\n originalFilename = result.filename;\n imageContentType = await detectImageContentType(imageBuffer);\n runtime.log?.(`[wecom] Image downloaded: size=${imageBuffer.length}, contentType=${imageContentType}, filename=${originalFilename ?? '(none)'}`);\n } catch (sdkError) {\n // 如果 SDK 方法失败,回退到原有方式(带超时保护)\n runtime.log?.(`[wecom] SDK download failed, fallback: ${String(sdkError)}`);\n const fetched = await withTimeout(\n core.channel.media.fetchRemoteMedia({ url: imageUrl }),\n IMAGE_DOWNLOAD_TIMEOUT_MS,\n `Manual image download timed out: ${imageUrl}`,\n ) as { buffer: Buffer; contentType?: string };\n runtime.log?.(`[wecom] Image fetched: contentType=${fetched.contentType}, size=${fetched.buffer.length}`);\n\n imageBuffer = fetched.buffer;\n imageContentType = fetched.contentType ?? \"application/octet-stream\";\n const isValidImage = await isImageBuffer(fetched.buffer);\n\n if (!isValidImage) {\n runtime.log?.(`[wecom] WARN: Downloaded data is not a valid image format`);\n }\n }\n\n const saved = await core.channel.media.saveMediaBuffer(\n imageBuffer,\n imageContentType,\n \"inbound\",\n maxBytes,\n originalFilename,\n );\n mediaList.push({ path: saved.path, contentType: saved.contentType });\n runtime.log?.(`[wecom][plugin] Image saved: path=${saved.path}, contentType=${saved.contentType}`);\n } catch (err) {\n runtime.error?.(`[wecom] Failed to download image: ${String(err)}`);\n }\n }\n\n return mediaList;\n}\n\n/**\n * 下载并保存所有文件到本地,每个文件的下载带超时保护\n */\nexport async function downloadAndSaveFiles(params: {\n fileUrls: string[];\n fileAesKeys?: Map<string, string>;\n account: ResolvedWeComAccount;\n config: OpenClawConfig;\n runtime: RuntimeEnv;\n wsClient: WSClient;\n}): Promise<Array<{ path: string; contentType?: string }>> {\n const { fileUrls, config, runtime, wsClient } = params;\n const core = getWeComRuntime();\n const mediaList: Array<{ path: string; contentType?: string }> = [];\n\n for (const fileUrl of fileUrls) {\n try {\n runtime.log?.(`[wecom] Downloading file: url=${fileUrl}`);\n const mediaMaxMb = config.agents?.defaults?.mediaMaxMb ?? DEFAULT_MEDIA_MAX_MB;\n const maxBytes = mediaMaxMb * 1024 * 1024;\n\n let fileBuffer: Buffer;\n let fileContentType: string;\n let originalFilename: string | undefined;\n const fileAesKey = params.fileAesKeys?.get(fileUrl);\n\n try {\n // 使用 SDK 的 downloadFile 方法下载(带超时保护)\n const result = await withTimeout(\n wsClient.downloadFile(fileUrl, fileAesKey),\n FILE_DOWNLOAD_TIMEOUT_MS,\n `File download timed out: ${fileUrl}`,\n );\n fileBuffer = result.buffer;\n originalFilename = result.filename;\n\n // 检测文件类型\n const type = await fileTypeFromBuffer(fileBuffer);\n fileContentType = type?.mime ?? \"application/octet-stream\";\n runtime.log?.(`[wecom] File downloaded: size=${fileBuffer.length}, contentType=${fileContentType}, filename=${originalFilename ?? '(none)'}`);\n } catch (sdkError) {\n // 如果 SDK 方法失败,回退到 fetchRemoteMedia(带超时保护)\n runtime.log?.(`[wecom] SDK file download failed, fallback: ${String(sdkError)}`);\n const fetched = await withTimeout(\n core.channel.media.fetchRemoteMedia({ url: fileUrl }),\n FILE_DOWNLOAD_TIMEOUT_MS,\n `Manual file download timed out: ${fileUrl}`,\n ) as { buffer: Buffer; contentType?: string };\n runtime.log?.(`[wecom] File fetched: contentType=${fetched.contentType}, size=${fetched.buffer.length}`);\n\n fileBuffer = fetched.buffer;\n fileContentType = fetched.contentType ?? \"application/octet-stream\";\n }\n\n const saved = await core.channel.media.saveMediaBuffer(\n fileBuffer,\n fileContentType,\n \"inbound\",\n maxBytes,\n originalFilename,\n );\n mediaList.push({ path: saved.path, contentType: saved.contentType });\n runtime.log?.(`[wecom][plugin] File saved: path=${saved.path}, contentType=${saved.contentType}`);\n } catch (err) {\n runtime.error?.(`[wecom] Failed to download file: ${String(err)}`);\n }\n }\n\n return mediaList;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"media-uploader.js","sources":["../../../../../src/media-uploader.ts"],"sourcesContent":["/**\n * 企业微信出站媒体上传工具模块\n *\n * 负责:\n * - 从 mediaUrl 加载文件 buffer(远程 URL 或本地路径均支持)\n * - 检测 MIME 类型并映射为企微媒体类型\n * - 文件大小检查与降级策略\n */\n\nimport type { WeComMediaType, WSClient, WsFrameHeaders } from \"@wecom/aibot-node-sdk\";\nimport {\n loadOutboundMediaFromUrl,\n detectMime,\n type WebMediaResult,\n} from \"./openclaw-compat.js\";\nimport {\n IMAGE_MAX_BYTES,\n VIDEO_MAX_BYTES,\n VOICE_MAX_BYTES,\n FILE_MAX_BYTES,\n ABSOLUTE_MAX_BYTES,\n} from \"./const.js\";\n\n// ============================================================================\n// 类型定义\n// ============================================================================\n\n/** 媒体文件解析结果 */\nexport interface ResolvedMedia {\n /** 文件数据 */\n buffer: Buffer;\n /** 检测到的 MIME 类型 */\n contentType: string;\n /** 文件名(从 URL 提取或默认生成) */\n fileName: string;\n}\n\n/** 文件大小检查结果 */\nexport interface FileSizeCheckResult {\n /** 最终确定的企微媒体类型(可能被降级) */\n finalType: WeComMediaType;\n /** 是否需要拒绝(超过绝对限制) */\n shouldReject: boolean;\n /** 拒绝原因(仅 shouldReject=true 时有值) */\n rejectReason?: string;\n /** 是否发生了降级 */\n downgraded: boolean;\n /** 降级说明(仅 downgraded=true 时有值) */\n downgradeNote?: string;\n}\n\n// ============================================================================\n// MIME → 企微媒体类型映射\n// ============================================================================\n\n/**\n * 根据 MIME 类型检测企微媒体类型\n *\n * @param mimeType - MIME 类型字符串\n * @returns 企微媒体类型\n */\nexport function detectWeComMediaType(mimeType: string): WeComMediaType {\n const mime = mimeType.toLowerCase();\n\n // 图片类型\n if (mime.startsWith(\"image/\")) {\n return \"image\";\n }\n\n // 视频类型\n if (mime.startsWith(\"video/\")) {\n return \"video\";\n }\n\n // 语音类型\n if (\n mime.startsWith(\"audio/\") ||\n mime === \"application/ogg\" // OGG 音频容器\n ) {\n return \"voice\";\n }\n\n // 其他类型默认为文件\n return \"file\";\n}\n\n// ============================================================================\n// 媒体文件加载\n// ============================================================================\n\n/**\n * 从 mediaUrl 加载媒体文件\n *\n * 支持远程 URL(http/https)和本地路径(file:// 或绝对路径),\n * 利用 openclaw plugin-sdk 的 loadOutboundMediaFromUrl 统一处理。\n *\n * @param mediaUrl - 媒体文件的 URL 或本地路径\n * @param mediaLocalRoots - 允许读取本地文件的安全白名单目录\n * @returns 解析后的媒体文件信息\n */\nexport async function resolveMediaFile(\n mediaUrl: string,\n mediaLocalRoots?: readonly string[],\n): Promise<ResolvedMedia> {\n // 使用兼容层加载媒体文件(优先 SDK,不可用时 fallback)\n // 传入足够大的 maxBytes,由我们自己在后续步骤做大小检查\n const result: WebMediaResult = await loadOutboundMediaFromUrl(mediaUrl, {\n maxBytes: ABSOLUTE_MAX_BYTES,\n mediaLocalRoots,\n });\n\n if (!result.buffer || result.buffer.length === 0) {\n throw new Error(`Failed to load media from ${mediaUrl}: empty buffer`);\n }\n\n // 检测真实 MIME 类型\n let contentType = result.contentType || \"application/octet-stream\";\n\n // 如果没有返回准确的 contentType,尝试通过 buffer 魔术字节检测\n if (\n contentType === \"application/octet-stream\" ||\n contentType === \"text/plain\"\n ) {\n const detected = await detectMime(result.buffer);\n if (detected) {\n contentType = detected;\n }\n }\n\n // 提取文件名\n const fileName = extractFileName(mediaUrl, result.fileName, contentType);\n\n return {\n buffer: result.buffer,\n contentType,\n fileName,\n };\n}\n\n// ============================================================================\n// 文件大小检查与降级\n// ============================================================================\n\n/** 企微语音消息仅支持 AMR 格式 */\nconst VOICE_SUPPORTED_MIMES = new Set([\"audio/amr\"]);\n\n/**\n * 检查文件大小并执行降级策略\n *\n * 降级规则:\n * - voice 非 AMR 格式 → 降级为 file(企微后台仅支持 AMR)\n * - image 超过 10MB → 降级为 file\n * - video 超过 10MB → 降级为 file\n * - voice 超过 2MB → 降级为 file\n * - file 超过 20MB → 拒绝发送\n *\n * @param fileSize - 文件大小(字节)\n * @param detectedType - 检测到的企微媒体类型\n * @param contentType - 文件的 MIME 类型(用于语音格式校验)\n * @returns 大小检查结果\n */\nexport function applyFileSizeLimits(\n fileSize: number,\n detectedType: WeComMediaType,\n contentType?: string,\n): FileSizeCheckResult {\n const fileSizeMB = (fileSize / (1024 * 1024)).toFixed(2);\n\n // 先检查绝对上限(20MB)\n if (fileSize > ABSOLUTE_MAX_BYTES) {\n return {\n finalType: detectedType,\n shouldReject: true,\n rejectReason: `文件大小 ${fileSizeMB}MB 超过了企业微信允许的最大限制 20MB,无法发送。请尝试压缩文件或减小文件大小。`,\n downgraded: false,\n };\n }\n\n // 按类型检查大小限制\n switch (detectedType) {\n case \"image\":\n if (fileSize > IMAGE_MAX_BYTES) {\n return {\n finalType: \"file\",\n shouldReject: false,\n downgraded: true,\n downgradeNote: `图片大小 ${fileSizeMB}MB 超过 10MB 限制,已转为文件格式发送`,\n };\n }\n break;\n\n case \"video\":\n if (fileSize > VIDEO_MAX_BYTES) {\n return {\n finalType: \"file\",\n shouldReject: false,\n downgraded: true,\n downgradeNote: `视频大小 ${fileSizeMB}MB 超过 10MB 限制,已转为文件格式发送`,\n };\n }\n break;\n\n case \"voice\":\n // 企微语音消息仅支持 AMR 格式,非 AMR 一律降级为文件\n if (contentType && !VOICE_SUPPORTED_MIMES.has(contentType.toLowerCase())) {\n return {\n finalType: \"file\",\n shouldReject: false,\n downgraded: true,\n downgradeNote: `语音格式 ${contentType} 不支持,企微仅支持 AMR 格式,已转为文件格式发送`,\n };\n }\n if (fileSize > VOICE_MAX_BYTES) {\n return {\n finalType: \"file\",\n shouldReject: false,\n downgraded: true,\n downgradeNote: `语音大小 ${fileSizeMB}MB 超过 2MB 限制,已转为文件格式发送`,\n };\n }\n break;\n\n case \"file\":\n // file 类型在绝对上限内即可\n break;\n }\n\n // 无需降级\n return {\n finalType: detectedType,\n shouldReject: false,\n downgraded: false,\n };\n}\n\n// ============================================================================\n// 辅助函数\n// ============================================================================\n\n/**\n * 从 URL/路径中提取文件名\n */\nfunction extractFileName(\n mediaUrl: string,\n providedFileName?: string,\n contentType?: string,\n): string {\n // 优先使用提供的文件名\n if (providedFileName) {\n return providedFileName;\n }\n\n // 尝试从 URL 中提取\n try {\n const urlObj = new URL(mediaUrl, \"file://\");\n const pathParts = urlObj.pathname.split(\"/\");\n const lastPart = pathParts[pathParts.length - 1];\n if (lastPart && lastPart.includes(\".\")) {\n return decodeURIComponent(lastPart);\n }\n } catch {\n // 尝试作为普通路径处理\n const parts = mediaUrl.split(\"/\");\n const lastPart = parts[parts.length - 1];\n if (lastPart && lastPart.includes(\".\")) {\n return lastPart;\n }\n }\n\n // 使用 MIME 类型生成默认文件名\n const ext = mimeToExtension(contentType || \"application/octet-stream\");\n return `media_${Date.now()}${ext}`;\n}\n\n/**\n * MIME 类型转文件扩展名\n */\nfunction mimeToExtension(mime: string): string {\n const map: Record<string, string> = {\n \"image/jpeg\": \".jpg\",\n \"image/png\": \".png\",\n \"image/gif\": \".gif\",\n \"image/webp\": \".webp\",\n \"image/bmp\": \".bmp\",\n \"image/svg+xml\": \".svg\",\n \"video/mp4\": \".mp4\",\n \"video/quicktime\": \".mov\",\n \"video/x-msvideo\": \".avi\",\n \"video/webm\": \".webm\",\n \"audio/mpeg\": \".mp3\",\n \"audio/ogg\": \".ogg\",\n \"audio/wav\": \".wav\",\n \"audio/amr\": \".amr\",\n \"audio/aac\": \".aac\",\n \"application/pdf\": \".pdf\",\n \"application/zip\": \".zip\",\n \"application/msword\": \".doc\",\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\":\n \".docx\",\n \"application/vnd.ms-excel\": \".xls\",\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\":\n \".xlsx\",\n \"text/plain\": \".txt\",\n };\n return map[mime] || \".bin\";\n}\n\n// ============================================================================\n// 公共媒体上传+发送流程\n// ============================================================================\n\n/** uploadAndSendMedia 的参数 */\nexport interface UploadAndSendMediaOptions {\n /** WSClient 实例 */\n wsClient: WSClient;\n /** 媒体文件的 URL 或本地路径 */\n mediaUrl: string;\n /** 目标会话 ID(用于 aibot_send_msg 主动发送) */\n chatId: string;\n /** 允许读取本地文件的安全白名单目录 */\n mediaLocalRoots?: readonly string[];\n /** 日志函数 */\n log?: (...args: any[]) => void;\n /** 错误日志函数 */\n errorLog?: (...args: any[]) => void;\n}\n\n/** uploadAndSendMedia 的返回结果 */\nexport interface UploadAndSendMediaResult {\n /** 是否发送成功 */\n ok: boolean;\n /** 发送后返回的 messageId */\n messageId?: string;\n /** 最终的企微媒体类型 */\n finalType?: WeComMediaType;\n /** 是否被拒绝(文件过大) */\n rejected?: boolean;\n /** 拒绝原因 */\n rejectReason?: string;\n /** 是否发生了降级 */\n downgraded?: boolean;\n /** 降级说明 */\n downgradeNote?: string;\n /** 错误信息 */\n error?: string;\n}\n\n/**\n * 公共媒体上传+发送流程\n *\n * 统一处理:resolveMediaFile → detectType → sizeCheck → uploadMedia → sendMediaMessage\n * 媒体消息统一走 aibot_send_msg 主动发送,避免多文件场景下 reqId 只能用一次的问题。\n * channel.ts 的 sendMedia 和 monitor.ts 的 deliver 回调都使用此函数。\n */\nexport async function uploadAndSendMedia(\n options: UploadAndSendMediaOptions,\n): Promise<UploadAndSendMediaResult> {\n const { wsClient, mediaUrl, chatId, mediaLocalRoots, log, errorLog } = options;\n\n try {\n // 1. 加载媒体文件\n log?.(`[wecom] Uploading media: url=${mediaUrl}`);\n const media = await resolveMediaFile(mediaUrl, mediaLocalRoots);\n\n // 2. 检测企微媒体类型\n const detectedType = detectWeComMediaType(media.contentType);\n\n // 3. 文件大小检查与降级策略\n const sizeCheck = applyFileSizeLimits(media.buffer.length, detectedType, media.contentType);\n\n if (sizeCheck.shouldReject) {\n errorLog?.(`[wecom] Media rejected: ${sizeCheck.rejectReason}`);\n return {\n ok: false,\n rejected: true,\n rejectReason: sizeCheck.rejectReason,\n finalType: sizeCheck.finalType,\n };\n }\n\n const finalType = sizeCheck.finalType;\n\n // 4. 分片上传获取 media_id\n const uploadResult = await wsClient.uploadMedia(media.buffer, {\n type: finalType,\n filename: media.fileName,\n });\n log?.(`[wecom] Media uploaded: media_id=${uploadResult.media_id}, type=${finalType}`);\n\n // 5. 统一通过 aibot_send_msg 主动发送媒体消息\n const result = await wsClient.sendMediaMessage(chatId, finalType, uploadResult.media_id);\n const messageId = result?.headers?.req_id ?? `wecom-media-${Date.now()}`;\n log?.(`[wecom] Media sent via sendMediaMessage: chatId=${chatId}, type=${finalType}`);\n\n return {\n ok: true,\n messageId,\n finalType,\n downgraded: sizeCheck.downgraded,\n downgradeNote: sizeCheck.downgradeNote,\n };\n } catch (err) {\n const errMsg = String(err);\n errorLog?.(`[wecom] Failed to upload/send media: url=${mediaUrl}, error=${errMsg}`);\n return {\n ok: false,\n error: errMsg,\n };\n }\n}\n\n// ============================================================================\n// 被动回复媒体上传+发送流程\n// ============================================================================\n\n/** uploadAndReplyMedia 的参数 */\nexport interface UploadAndReplyMediaOptions {\n /** WSClient 实例 */\n wsClient: WSClient;\n /** 媒体文件的 URL 或本地路径 */\n mediaUrl: string;\n /** 原始 WebSocket 帧(用于 aibot_respond_msg 被动回复,携带 req_id) */\n frame: WsFrameHeaders;\n /** 允许读取本地文件的安全白名单目录 */\n mediaLocalRoots?: readonly string[];\n /** 日志函数 */\n log?: (...args: any[]) => void;\n /** 错误日志函数 */\n errorLog?: (...args: any[]) => void;\n}\n\n/**\n * 被动回复媒体上传+发送流程\n *\n * 统一处理:resolveMediaFile → detectType → sizeCheck → uploadMedia → replyMedia\n * 通过 aibot_respond_msg 被动回复通道发送媒体消息,可以覆盖之前的 THINKING_MESSAGE。\n *\n * 适用场景:回包只有媒体没有文本时,第一个媒体文件用此方法发送以清理 thinking 状态。\n */\nexport async function uploadAndReplyMedia(\n options: UploadAndReplyMediaOptions,\n): Promise<UploadAndSendMediaResult> {\n const { wsClient, mediaUrl, frame, mediaLocalRoots, log, errorLog } = options;\n\n try {\n // 1. 加载媒体文件\n log?.(`[wecom] Uploading media (reply mode): url=${mediaUrl}`);\n const media = await resolveMediaFile(mediaUrl, mediaLocalRoots);\n\n // 2. 检测企微媒体类型\n const detectedType = detectWeComMediaType(media.contentType);\n\n // 3. 文件大小检查与降级策略\n const sizeCheck = applyFileSizeLimits(media.buffer.length, detectedType, media.contentType);\n\n if (sizeCheck.shouldReject) {\n errorLog?.(`[wecom] Media rejected: ${sizeCheck.rejectReason}`);\n return {\n ok: false,\n rejected: true,\n rejectReason: sizeCheck.rejectReason,\n finalType: sizeCheck.finalType,\n };\n }\n\n const finalType = sizeCheck.finalType;\n\n // 4. 分片上传获取 media_id\n const uploadResult = await wsClient.uploadMedia(media.buffer, {\n type: finalType,\n filename: media.fileName,\n });\n log?.(`[wecom] Media uploaded: media_id=${uploadResult.media_id}, type=${finalType}`);\n\n // 5. 通过 aibot_respond_msg 被动回复发送媒体消息(会覆盖 THINKING_MESSAGE)\n const result = await wsClient.replyMedia(frame, finalType, uploadResult.media_id);\n const messageId = result?.headers?.req_id ?? `wecom-reply-media-${Date.now()}`;\n log?.(`[wecom] Media sent via replyMedia (passive reply): type=${finalType}`);\n\n return {\n ok: true,\n messageId,\n finalType,\n downgraded: sizeCheck.downgraded,\n downgradeNote: sizeCheck.downgradeNote,\n };\n } catch (err) {\n const errMsg = String(err);\n errorLog?.(`[wecom] Failed to upload/reply media: url=${mediaUrl}, error=${errMsg}`);\n return {\n ok: false,\n error: errMsg,\n };\n }\n}\n"],"names":[],"mappings":";;;AAAA;;;;;;;AAOG;AA4CH;AACA;AACA;AAEA;;;;;AAKG;AACG,SAAU,oBAAoB,CAAC,QAAgB,EAAA;AACnD,IAAA,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,EAAE;;AAGnC,IAAA,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;AAC7B,QAAA,OAAO,OAAO;IAChB;;AAGA,IAAA,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;AAC7B,QAAA,OAAO,OAAO;IAChB;;AAGA,IAAA,IACE,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QACzB,IAAI,KAAK,iBAAiB;MAC1B;AACA,QAAA,OAAO,OAAO;IAChB;;AAGA,IAAA,OAAO,MAAM;AACf;AAEA;AACA;AACA;AAEA;;;;;;;;;AASG;AACI,eAAe,gBAAgB,CACpC,QAAgB,EAChB,eAAmC,EAAA;;;AAInC,IAAA,MAAM,MAAM,GAAmB,MAAM,wBAAwB,CAAC,QAAQ,EAAE;AACtE,QAAA,QAAQ,EAAE,kBAAkB;QAC5B,eAAe;AAChB,KAAA,CAAC;AAEF,IAAA,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE;AAChD,QAAA,MAAM,IAAI,KAAK,CAAC,6BAA6B,QAAQ,CAAA,cAAA,CAAgB,CAAC;IACxE;;AAGA,IAAA,IAAI,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,0BAA0B;;IAGlE,IACE,WAAW,KAAK,0BAA0B;QAC1C,WAAW,KAAK,YAAY,EAC5B;QACA,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC;QAChD,IAAI,QAAQ,EAAE;YACZ,WAAW,GAAG,QAAQ;QACxB;IACF;;AAGA,IAAA,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC;IAExE,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,WAAW;QACX,QAAQ;KACT;AACH;AAEA;AACA;AACA;AAEA;AACA,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC;AAEpD;;;;;;;;;;;;;;AAcG;SACa,mBAAmB,CACjC,QAAgB,EAChB,YAA4B,EAC5B,WAAoB,EAAA;AAEpB,IAAA,MAAM,UAAU,GAAG,CAAC,QAAQ,IAAI,IAAI,GAAG,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;;AAGxD,IAAA,IAAI,QAAQ,GAAG,kBAAkB,EAAE;QACjC,OAAO;AACL,YAAA,SAAS,EAAE,YAAY;AACvB,YAAA,YAAY,EAAE,IAAI;YAClB,YAAY,EAAE,CAAA,KAAA,EAAQ,UAAU,CAAA,2CAAA,CAA6C;AAC7E,YAAA,UAAU,EAAE,KAAK;SAClB;IACH;;IAGA,QAAQ,YAAY;AAClB,QAAA,KAAK,OAAO;AACV,YAAA,IAAI,QAAQ,GAAG,eAAe,EAAE;gBAC9B,OAAO;AACL,oBAAA,SAAS,EAAE,MAAM;AACjB,oBAAA,YAAY,EAAE,KAAK;AACnB,oBAAA,UAAU,EAAE,IAAI;oBAChB,aAAa,EAAE,CAAA,KAAA,EAAQ,UAAU,CAAA,uBAAA,CAAyB;iBAC3D;YACH;YACA;AAEF,QAAA,KAAK,OAAO;AACV,YAAA,IAAI,QAAQ,GAAG,eAAe,EAAE;gBAC9B,OAAO;AACL,oBAAA,SAAS,EAAE,MAAM;AACjB,oBAAA,YAAY,EAAE,KAAK;AACnB,oBAAA,UAAU,EAAE,IAAI;oBAChB,aAAa,EAAE,CAAA,KAAA,EAAQ,UAAU,CAAA,uBAAA,CAAyB;iBAC3D;YACH;YACA;AAEF,QAAA,KAAK,OAAO;;AAEV,YAAA,IAAI,WAAW,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,EAAE;gBACxE,OAAO;AACL,oBAAA,SAAS,EAAE,MAAM;AACjB,oBAAA,YAAY,EAAE,KAAK;AACnB,oBAAA,UAAU,EAAE,IAAI;oBAChB,aAAa,EAAE,CAAA,KAAA,EAAQ,WAAW,CAAA,2BAAA,CAA6B;iBAChE;YACH;AACA,YAAA,IAAI,QAAQ,GAAG,eAAe,EAAE;gBAC9B,OAAO;AACL,oBAAA,SAAS,EAAE,MAAM;AACjB,oBAAA,YAAY,EAAE,KAAK;AACnB,oBAAA,UAAU,EAAE,IAAI;oBAChB,aAAa,EAAE,CAAA,KAAA,EAAQ,UAAU,CAAA,sBAAA,CAAwB;iBAC1D;YACH;YACA;;;IAQJ,OAAO;AACL,QAAA,SAAS,EAAE,YAAY;AACvB,QAAA,YAAY,EAAE,KAAK;AACnB,QAAA,UAAU,EAAE,KAAK;KAClB;AACH;AAEA;AACA;AACA;AAEA;;AAEG;AACH,SAAS,eAAe,CACtB,QAAgB,EAChB,gBAAyB,EACzB,WAAoB,EAAA;;IAGpB,IAAI,gBAAgB,EAAE;AACpB,QAAA,OAAO,gBAAgB;IACzB;;AAGA,IAAA,IAAI;QACF,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC;QAC3C,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC;QAC5C,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;QAChD,IAAI,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AACtC,YAAA,OAAO,kBAAkB,CAAC,QAAQ,CAAC;QACrC;IACF;AAAE,IAAA,MAAM;;QAEN,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC;QACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QACxC,IAAI,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AACtC,YAAA,OAAO,QAAQ;QACjB;IACF;;IAGA,MAAM,GAAG,GAAG,eAAe,CAAC,WAAW,IAAI,0BAA0B,CAAC;IACtE,OAAO,CAAA,MAAA,EAAS,IAAI,CAAC,GAAG,EAAE,CAAA,EAAG,GAAG,EAAE;AACpC;AAEA;;AAEG;AACH,SAAS,eAAe,CAAC,IAAY,EAAA;AACnC,IAAA,MAAM,GAAG,GAA2B;AAClC,QAAA,YAAY,EAAE,MAAM;AACpB,QAAA,WAAW,EAAE,MAAM;AACnB,QAAA,WAAW,EAAE,MAAM;AACnB,QAAA,YAAY,EAAE,OAAO;AACrB,QAAA,WAAW,EAAE,MAAM;AACnB,QAAA,eAAe,EAAE,MAAM;AACvB,QAAA,WAAW,EAAE,MAAM;AACnB,QAAA,iBAAiB,EAAE,MAAM;AACzB,QAAA,iBAAiB,EAAE,MAAM;AACzB,QAAA,YAAY,EAAE,OAAO;AACrB,QAAA,YAAY,EAAE,MAAM;AACpB,QAAA,WAAW,EAAE,MAAM;AACnB,QAAA,WAAW,EAAE,MAAM;AACnB,QAAA,WAAW,EAAE,MAAM;AACnB,QAAA,WAAW,EAAE,MAAM;AACnB,QAAA,iBAAiB,EAAE,MAAM;AACzB,QAAA,iBAAiB,EAAE,MAAM;AACzB,QAAA,oBAAoB,EAAE,MAAM;AAC5B,QAAA,yEAAyE,EACvE,OAAO;AACT,QAAA,0BAA0B,EAAE,MAAM;AAClC,QAAA,mEAAmE,EACjE,OAAO;AACT,QAAA,YAAY,EAAE,MAAM;KACrB;AACD,IAAA,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,MAAM;AAC5B;AA0CA;;;;;;AAMG;AACI,eAAe,kBAAkB,CACtC,OAAkC,EAAA;AAElC,IAAA,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,OAAO;AAE9E,IAAA,IAAI;;AAEF,QAAA,GAAG,GAAG,CAAA,6BAAA,EAAgC,QAAQ,CAAA,CAAE,CAAC;QACjD,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,QAAQ,EAAE,eAAe,CAAC;;QAG/D,MAAM,YAAY,GAAG,oBAAoB,CAAC,KAAK,CAAC,WAAW,CAAC;;AAG5D,QAAA,MAAM,SAAS,GAAG,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC;AAE3F,QAAA,IAAI,SAAS,CAAC,YAAY,EAAE;YAC1B,QAAQ,GAAG,CAAA,wBAAA,EAA2B,SAAS,CAAC,YAAY,CAAA,CAAE,CAAC;YAC/D,OAAO;AACL,gBAAA,EAAE,EAAE,KAAK;AACT,gBAAA,QAAQ,EAAE,IAAI;gBACd,YAAY,EAAE,SAAS,CAAC,YAAY;gBACpC,SAAS,EAAE,SAAS,CAAC,SAAS;aAC/B;QACH;AAEA,QAAA,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS;;QAGrC,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE;AAC5D,YAAA,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,KAAK,CAAC,QAAQ;AACzB,SAAA,CAAC;QACF,GAAG,GAAG,CAAA,iCAAA,EAAoC,YAAY,CAAC,QAAQ,CAAA,OAAA,EAAU,SAAS,CAAA,CAAE,CAAC;;AAGrF,QAAA,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,CAAC,QAAQ,CAAC;AACxF,QAAA,MAAM,SAAS,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,CAAA,YAAA,EAAe,IAAI,CAAC,GAAG,EAAE,EAAE;QACxE,GAAG,GAAG,CAAA,gDAAA,EAAmD,MAAM,UAAU,SAAS,CAAA,CAAE,CAAC;QAErF,OAAO;AACL,YAAA,EAAE,EAAE,IAAI;YACR,SAAS;YACT,SAAS;YACT,UAAU,EAAE,SAAS,CAAC,UAAU;YAChC,aAAa,EAAE,SAAS,CAAC,aAAa;SACvC;IACH;IAAE,OAAO,GAAG,EAAE;AACZ,QAAA,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC;QAC1B,QAAQ,GAAG,CAAA,yCAAA,EAA4C,QAAQ,WAAW,MAAM,CAAA,CAAE,CAAC;QACnF,OAAO;AACL,YAAA,EAAE,EAAE,KAAK;AACT,YAAA,KAAK,EAAE,MAAM;SACd;IACH;AACF;;;;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"message-parser.js","sources":["../../../../../src/message-parser.ts"],"sourcesContent":["/**\n * 企业微信消息内容解析模块\n *\n * 负责从 WsFrame 中提取文本、图片、引用等内容\n */\n\n// ============================================================================\n// 消息体类型(来自 SDK WsFrame.body)\n// ============================================================================\n\nexport interface MessageBody {\n msgid: string;\n aibotid?: string;\n chatid?: string;\n chattype: \"single\" | \"group\";\n from: {\n corpid?: string;\n userid: string;\n };\n response_url?: string;\n msgtype: string;\n text?: {\n content: string;\n };\n image?: {\n url?: string;\n aeskey?: string;\n };\n voice?: {\n content?: string;\n };\n mixed?: {\n msg_item: Array<{\n msgtype: \"text\" | \"image\";\n text?: { content: string };\n image?: { url?: string; aeskey?: string };\n }>;\n };\n file?: {\n url?: string;\n aeskey?: string;\n };\n quote?: {\n msgtype: string;\n text?: { content: string };\n voice?: { content: string };\n image?: { url?: string; aeskey?: string };\n file?: { url?: string; aeskey?: string };\n };\n event?: {\n eventtype?: string;\n template_card_event?: {\n card_type?: string;\n event_key?: string;\n task_id?: string;\n selected_items?: {\n selected_item?: Array<{\n question_key?: string;\n option_ids?: {\n option_id?: string[];\n };\n }>;\n };\n };\n };\n}\n\n// ============================================================================\n// 解析结果类型\n// ============================================================================\n\nexport interface ParsedMessageContent {\n textParts: string[];\n imageUrls: string[];\n imageAesKeys: Map<string, string>;\n fileUrls: string[];\n fileAesKeys: Map<string, string>;\n quoteContent: string | undefined;\n}\n\n// ============================================================================\n// 解析函数\n// ============================================================================\n\n/**\n * 将模板卡片事件回调格式化为可继续路由给大模型的文本。\n *\n * 这样后续 Agent 可以直接从 question_key / option_id 中理解用户的真实选择。\n */\nfunction buildTemplateCardEventText(body: MessageBody): string | undefined {\n const templateCardEvent = body.event?.template_card_event;\n if (\n body.msgtype !== \"event\" ||\n body.event?.eventtype !== \"template_card_event\" ||\n !templateCardEvent\n ) {\n return undefined;\n }\n\n const selectedItems = templateCardEvent.selected_items?.selected_item ?? [];\n const selectedLines = selectedItems.map((item) => {\n const questionKey = item.question_key?.trim() || \"unknown_question\";\n const optionIds = item.option_ids?.option_id?.filter(Boolean) ?? [];\n return `- ${questionKey}: ${optionIds.length > 0 ? optionIds.join(\", \") : \"(未选择)\"}`;\n });\n\n const senderUserId = body.from?.userid || \"\";\n const senderCorpId = body.from?.corpid || \"\";\n const chatId = body.chatid || senderUserId;\n\n return [\n \"[企业微信模板卡片回调]\",\n `event_type(事件类型): template_card_event`,\n body.msgid ? `msgid(消息 id): ${body.msgid}` : undefined,\n body.aibotid ? `aibotid(机器人 id): ${body.aibotid}` : undefined,\n body.chattype ? `chat_type(会话类型): ${body.chattype}` : undefined,\n chatId ? `chat_id(会话 id): ${chatId}` : undefined,\n senderCorpId ? `from.corpid(企业 id): ${senderCorpId}` : undefined,\n senderUserId ? `from.userid(发送人 id): ${senderUserId}` : undefined,\n senderUserId ? `sender_userid(发送人 id): ${senderUserId}` : undefined,\n templateCardEvent.card_type ? `card_type(卡片类型): ${templateCardEvent.card_type}` : undefined,\n templateCardEvent.event_key ? `event_key(事件 key): ${templateCardEvent.event_key}` : undefined,\n templateCardEvent.task_id ? `task_id(任务 id): ${templateCardEvent.task_id}` : undefined,\n selectedLines.length > 0 ? \"selected_items(选择项):\" : \"selected_items(选择项): []\",\n ...selectedLines,\n ]\n .filter((line): line is string => Boolean(line))\n .join(\"\\n\");\n}\n\n/**\n * 解析消息内容(支持单条消息、图文混排、事件回调和引用消息)\n * @returns 提取的文本数组、图片URL数组和引用消息内容\n */\nexport function parseMessageContent(body: MessageBody): ParsedMessageContent {\n const textParts: string[] = [];\n const imageUrls: string[] = [];\n const imageAesKeys = new Map<string, string>();\n const fileUrls: string[] = [];\n const fileAesKeys = new Map<string, string>();\n let quoteContent: string | undefined;\n\n // 处理模板卡片事件回调\n \n if (body.msgtype === \"event\") {\n const eventText = buildTemplateCardEventText(body);\n if (eventText) {\n textParts.push(eventText);\n }\n return { textParts, imageUrls, imageAesKeys, fileUrls, fileAesKeys, quoteContent };\n }\n\n // 处理图文混排消息\n if (body.msgtype === \"mixed\" && body.mixed?.msg_item) {\n for (const item of body.mixed.msg_item) {\n if (item.msgtype === \"text\" && item.text?.content) {\n textParts.push(item.text.content);\n } else if (item.msgtype === \"image\" && item.image?.url) {\n imageUrls.push(item.image.url);\n if (item.image.aeskey) {\n imageAesKeys.set(item.image.url, item.image.aeskey);\n }\n }\n }\n } else {\n // 处理单条消息\n if (body.text?.content) {\n textParts.push(body.text.content);\n }\n // 处理语音消息(语音转文字后的文本内容)\n if (body.msgtype === \"voice\" && body.voice?.content) {\n textParts.push(body.voice.content);\n }\n if (body.image?.url) {\n imageUrls.push(body.image.url);\n if (body.image.aeskey) {\n imageAesKeys.set(body.image.url, body.image.aeskey);\n }\n }\n // 处理文件消息\n if (body.msgtype === \"file\" && body.file?.url) {\n fileUrls.push(body.file.url);\n if (body.file.aeskey) {\n fileAesKeys.set(body.file.url, body.file.aeskey);\n }\n }\n }\n\n // 处理引用消息\n if (body.quote) {\n if (body.quote.msgtype === \"text\" && body.quote.text?.content) {\n quoteContent = body.quote.text.content;\n } else if (body.quote.msgtype === \"voice\" && body.quote.voice?.content) {\n quoteContent = body.quote.voice.content;\n } else if (body.quote.msgtype === \"image\" && body.quote.image?.url) {\n // 引用的图片消息:将图片 URL 加入下载列表\n imageUrls.push(body.quote.image.url);\n if (body.quote.image.aeskey) {\n imageAesKeys.set(body.quote.image.url, body.quote.image.aeskey);\n }\n } else if (body.quote.msgtype === \"file\" && body.quote.file?.url) {\n // 引用的文件消息:将文件 URL 加入下载列表\n fileUrls.push(body.quote.file.url);\n if (body.quote.file.aeskey) {\n fileAesKeys.set(body.quote.file.url, body.quote.file.aeskey);\n }\n }\n }\n\n return { textParts, imageUrls, imageAesKeys, fileUrls, fileAesKeys, quoteContent };\n}\n"],"names":[],"mappings":"AAAA;;;;AAIG;AA4EH;AACA;AACA;AAEA;;;;AAIG;AACH,SAAS,0BAA0B,CAAC,IAAiB,EAAA;AACnD,IAAA,MAAM,iBAAiB,GAAG,IAAI,CAAC,KAAK,EAAE,mBAAmB;AACzD,IAAA,IACE,IAAI,CAAC,OAAO,KAAK,OAAO;AACxB,QAAA,IAAI,CAAC,KAAK,EAAE,SAAS,KAAK,qBAAqB;QAC/C,CAAC,iBAAiB,EAClB;AACA,QAAA,OAAO,SAAS;IAClB;IAEA,MAAM,aAAa,GAAG,iBAAiB,CAAC,cAAc,EAAE,aAAa,IAAI,EAAE;IAC3E,MAAM,aAAa,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,KAAI;QAC/C,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,kBAAkB;AACnE,QAAA,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE;QACnE,OAAO,CAAA,EAAA,EAAK,WAAW,CAAA,EAAA,EAAK,SAAS,CAAC,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,CAAA,CAAE;AACrF,IAAA,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE;IAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE;AAC5C,IAAA,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,YAAY;IAE1C,OAAO;QACL,cAAc;QACd,CAAA,qCAAA,CAAuC;AACvC,QAAA,IAAI,CAAC,KAAK,GAAG,CAAA,cAAA,EAAiB,IAAI,CAAC,KAAK,CAAA,CAAE,GAAG,SAAS;AACtD,QAAA,IAAI,CAAC,OAAO,GAAG,CAAA,iBAAA,EAAoB,IAAI,CAAC,OAAO,CAAA,CAAE,GAAG,SAAS;AAC7D,QAAA,IAAI,CAAC,QAAQ,GAAG,CAAA,iBAAA,EAAoB,IAAI,CAAC,QAAQ,CAAA,CAAE,GAAG,SAAS;QAC/D,MAAM,GAAG,CAAA,gBAAA,EAAmB,MAAM,CAAA,CAAE,GAAG,SAAS;QAChD,YAAY,GAAG,CAAA,oBAAA,EAAuB,YAAY,CAAA,CAAE,GAAG,SAAS;QAChE,YAAY,GAAG,CAAA,qBAAA,EAAwB,YAAY,CAAA,CAAE,GAAG,SAAS;QACjE,YAAY,GAAG,CAAA,uBAAA,EAA0B,YAAY,CAAA,CAAE,GAAG,SAAS;AACnE,QAAA,iBAAiB,CAAC,SAAS,GAAG,CAAA,iBAAA,EAAoB,iBAAiB,CAAC,SAAS,CAAA,CAAE,GAAG,SAAS;AAC3F,QAAA,iBAAiB,CAAC,SAAS,GAAG,CAAA,mBAAA,EAAsB,iBAAiB,CAAC,SAAS,CAAA,CAAE,GAAG,SAAS;AAC7F,QAAA,iBAAiB,CAAC,OAAO,GAAG,CAAA,gBAAA,EAAmB,iBAAiB,CAAC,OAAO,CAAA,CAAE,GAAG,SAAS;QACtF,aAAa,CAAC,MAAM,GAAG,CAAC,GAAG,sBAAsB,GAAG,yBAAyB;AAC7E,QAAA,GAAG,aAAa;AACjB;SACE,MAAM,CAAC,CAAC,IAAI,KAAqB,OAAO,CAAC,IAAI,CAAC;SAC9C,IAAI,CAAC,IAAI,CAAC;AACf;AAEA;;;AAGG;AACG,SAAU,mBAAmB,CAAC,IAAiB,EAAA;IACnD,MAAM,SAAS,GAAa,EAAE;IAC9B,MAAM,SAAS,GAAa,EAAE;AAC9B,IAAA,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB;IAC9C,MAAM,QAAQ,GAAa,EAAE;AAC7B,IAAA,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB;AAC7C,IAAA,IAAI,YAAgC;;AAIpC,IAAA,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,EAAE;AAC5B,QAAA,MAAM,SAAS,GAAG,0BAA0B,CAAC,IAAI,CAAC;QAClD,IAAI,SAAS,EAAE;AACb,YAAA,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC;QAC3B;AACA,QAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE;IACpF;;AAGA,IAAA,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,IAAI,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE;QACpD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE;AACtC,YAAA,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE;gBACjD,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;YACnC;AAAO,iBAAA,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,IAAI,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE;gBACtD,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;AAC9B,gBAAA,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;AACrB,oBAAA,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;gBACrD;YACF;QACF;IACF;SAAO;;AAEL,QAAA,IAAI,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE;YACtB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;QACnC;;AAEA,QAAA,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE;YACnD,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;QACpC;AACA,QAAA,IAAI,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE;YACnB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;AAC9B,YAAA,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;AACrB,gBAAA,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;YACrD;QACF;;AAEA,QAAA,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE;YAC7C,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AAC5B,YAAA,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;AACpB,gBAAA,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;YAClD;QACF;IACF;;AAGA,IAAA,IAAI,IAAI,CAAC,KAAK,EAAE;AACd,QAAA,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE;YAC7D,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO;QACxC;AAAO,aAAA,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,KAAK,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE;YACtE,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO;QACzC;AAAO,aAAA,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,KAAK,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE;;YAElE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;YACpC,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE;AAC3B,gBAAA,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;YACjE;QACF;AAAO,aAAA,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE;;YAEhE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;YAClC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE;AAC1B,gBAAA,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;YAC9D;QACF;IACF;AAEA,IAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE;AACpF;;;;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"message-sender.js","sources":["../../../../../src/message-sender.ts"],"sourcesContent":["/**\n * 企业微信消息发送模块\n *\n * 负责通过 WSClient 发送回复消息,包含超时保护\n */\n\nimport type { RuntimeEnv } from \"openclaw/plugin-sdk/runtime-env\";\nimport { type WSClient, type WsFrame, generateReqId } from \"@wecom/aibot-node-sdk\";\nimport { REPLY_SEND_TIMEOUT_MS } from \"./const.js\";\nimport { withTimeout } from \"./timeout.js\";\n\n// ============================================================================\n// 流式过期错误(errcode 846608)\n// ============================================================================\n\n/** 流式回复超时错误码(>6分钟未更新,服务端拒绝继续流式更新) */\nexport const STREAM_EXPIRED_ERRCODE = 846608;\n\n/**\n * 流式回复过期错误\n * 当服务端返回 errcode=846608 时抛出,表示流式消息已超过6分钟无法更新,\n * 调用方需降级为主动发送(sendMessage)方式回复。\n */\nexport class StreamExpiredError extends Error {\n readonly errcode = STREAM_EXPIRED_ERRCODE;\n constructor(message?: string) {\n super(message ?? `Stream message update expired (errcode=${STREAM_EXPIRED_ERRCODE})`);\n this.name = \"StreamExpiredError\";\n }\n}\n\n// ============================================================================\n// 消息发送\n// ============================================================================\n\n/**\n * 发送企业微信回复消息\n * 供 monitor 内部和 channel outbound 使用\n *\n * @returns messageId (streamId)\n */\nexport async function sendWeComReply(params: {\n wsClient: WSClient;\n frame: WsFrame;\n text?: string;\n runtime: RuntimeEnv;\n /** 是否为流式回复的最终消息,默认为 true */\n finish?: boolean;\n /** 指定 streamId,用于流式回复时保持相同的 streamId */\n streamId?: string;\n}): Promise<string> {\n const { wsClient, frame, text, runtime, finish = true, streamId: existingStreamId } = params;\n\n if (!text) {\n return \"\";\n }\n\n const streamId = existingStreamId || generateReqId(\"stream\");\n\n if (!wsClient.isConnected) {\n runtime.error?.(`[wecom] WSClient not connected, cannot send reply`);\n throw new Error(\"WSClient not connected\");\n }\n\n const body = frame.body as {\n msgtype?: string;\n chatid?: string;\n from?: {\n userid?: string;\n };\n };\n\n // 事件回调(aibot_event_callback)没有可用于 replyStream 的有效 req_id,\n // 对该场景改用主动发送 sendMessage,避免 846605 invalid req_id。\n if (body.msgtype === \"event\") {\n // 中间帧(thinking / 流式增量)直接跳过,仅在最终帧主动发一次文本。\n if (!finish) {\n runtime.log?.(`[plugin -> server] skip non-final stream for event callback, streamId=${streamId}`);\n return streamId;\n }\n\n const chatId = body.chatid || body.from?.userid;\n if (!chatId) {\n throw new Error(\"Missing chatId for event callback reply\");\n }\n\n await withTimeout(\n wsClient.sendMessage(chatId, {\n msgtype: \"markdown\",\n markdown: { content: text },\n }),\n REPLY_SEND_TIMEOUT_MS,\n `Event reply send timed out (streamId=${streamId})`,\n );\n runtime.log?.(`[plugin -> server] event-active-send chatId=${chatId}, finish=${finish}`);\n return streamId;\n }\n\n // 非事件消息,继续使用 replyStream(被动回复)\n // 使用 SDK 的 replyStream 方法发送消息,带超时保护\n try {\n await withTimeout(\n wsClient.replyStream(frame, streamId, text, finish),\n REPLY_SEND_TIMEOUT_MS,\n `Reply send timed out (streamId=${streamId})`,\n );\n } catch (err: any) {\n // 服务端返回 846608:流式消息超过6分钟无法更新,需降级为主动发送\n const errMsg = err?.errmsg || err?.message || String(err);\n if (\n err?.errcode === STREAM_EXPIRED_ERRCODE ||\n errMsg.includes(String(STREAM_EXPIRED_ERRCODE))\n ) {\n throw new StreamExpiredError(errMsg);\n }\n throw err;\n }\n runtime.log?.(`[plugin -> server] streamId=${streamId}, finish=${finish}`);\n\n return streamId;\n}\n"],"names":[],"mappings":";;;;AAAA;;;;AAIG;AAOH;AACA;AACA;AAEA;AACO,MAAM,sBAAsB,GAAG;AAEtC;;;;AAIG;AACG,MAAO,kBAAmB,SAAQ,KAAK,CAAA;AAE3C,IAAA,WAAA,CAAY,OAAgB,EAAA;AAC1B,QAAA,KAAK,CAAC,OAAO,IAAI,0CAA0C,sBAAsB,CAAA,CAAA,CAAG,CAAC;QAF9E,IAAA,CAAA,OAAO,GAAG,sBAAsB;AAGvC,QAAA,IAAI,CAAC,IAAI,GAAG,oBAAoB;IAClC;AACD;AAED;AACA;AACA;AAEA;;;;;AAKG;AACI,eAAe,cAAc,CAAC,MASpC,EAAA;AACC,IAAA,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,gBAAgB,EAAE,GAAG,MAAM;IAE5F,IAAI,CAAC,IAAI,EAAE;AACT,QAAA,OAAO,EAAE;IACX;IAEA,MAAM,QAAQ,GAAG,gBAAgB,IAAI,aAAa,CAAC,QAAQ,CAAC;AAE5D,IAAA,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE;AACzB,QAAA,OAAO,CAAC,KAAK,GAAG,CAAA,iDAAA,CAAmD,CAAC;AACpE,QAAA,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC;IAC3C;AAEA,IAAA,MAAM,IAAI,GAAG,KAAK,CAAC,IAMlB;;;AAID,IAAA,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,EAAE;;QAE5B,IAAI,CAAC,MAAM,EAAE;YACX,OAAO,CAAC,GAAG,GAAG,yEAAyE,QAAQ,CAAA,CAAE,CAAC;AAClG,YAAA,OAAO,QAAQ;QACjB;QAEA,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,EAAE,MAAM;QAC/C,IAAI,CAAC,MAAM,EAAE;AACX,YAAA,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC;QAC5D;AAEA,QAAA,MAAM,WAAW,CACf,QAAQ,CAAC,WAAW,CAAC,MAAM,EAAE;AAC3B,YAAA,OAAO,EAAE,UAAU;AACnB,YAAA,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;AAC5B,SAAA,CAAC,EACF,qBAAqB,EACrB,wCAAwC,QAAQ,CAAA,CAAA,CAAG,CACpD;QACD,OAAO,CAAC,GAAG,GAAG,CAAA,4CAAA,EAA+C,MAAM,CAAA,SAAA,EAAY,MAAM,CAAA,CAAE,CAAC;AACxF,QAAA,OAAO,QAAQ;IACjB;;;AAIA,IAAA,IAAI;QACF,MAAM,WAAW,CACf,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,EACnD,qBAAqB,EACrB,CAAA,+BAAA,EAAkC,QAAQ,CAAA,CAAA,CAAG,CAC9C;IACH;IAAE,OAAO,GAAQ,EAAE;;AAEjB,QAAA,MAAM,MAAM,GAAG,GAAG,EAAE,MAAM,IAAI,GAAG,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC;AACzD,QAAA,IACE,GAAG,EAAE,OAAO,KAAK,sBAAsB;YACvC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,EAC/C;AACA,YAAA,MAAM,IAAI,kBAAkB,CAAC,MAAM,CAAC;QACtC;AACA,QAAA,MAAM,GAAG;IACX;IACA,OAAO,CAAC,GAAG,GAAG,CAAA,4BAAA,EAA+B,QAAQ,CAAA,SAAA,EAAY,MAAM,CAAA,CAAE,CAAC;AAE1E,IAAA,OAAO,QAAQ;AACjB;;;;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"monitor.js","sources":["../../../../../src/monitor.ts"],"sourcesContent":["/**\n * 企业微信 WebSocket 监控器主模块\n *\n * 负责:\n * - 建立和管理 WebSocket 连接\n * - 协调消息处理流程(解析→策略检查→下载图片→路由回复)\n * - 资源生命周期管理\n *\n * 子模块:\n * - message-parser.ts : 消息内容解析\n * - message-sender.ts : 消息发送(带超时保护)\n * - media-handler.ts : 图片下载和保存(带超时保护)\n * - group-policy.ts : 群组访问控制\n * - dm-policy.ts : 私聊访问控制\n * - state-manager.ts : 全局状态管理(带 TTL 清理)\n * - timeout.ts : 超时工具\n */\n\nimport * as os from \"os\";\nimport * as path from \"path\";\nimport type { OpenClawConfig } from \"openclaw/plugin-sdk/core\";\nimport type { RuntimeEnv } from \"openclaw/plugin-sdk/runtime-env\";\nimport { WSClient, generateReqId, WSAuthFailureError, WSReconnectExhaustedError } from \"@wecom/aibot-node-sdk\";\nimport type { WsFrame, Logger, TemplateCard } from \"@wecom/aibot-node-sdk\";\nimport { getWeComRuntime } from \"./runtime.js\";\nimport { getDefaultMediaLocalRoots } from \"./openclaw-compat.js\";\nimport type { ResolvedWeComAccount, WeComConfig } from \"./utils.js\";\nimport {\n CHANNEL_ID,\n THINKING_MESSAGE,\n MEDIA_IMAGE_PLACEHOLDER,\n MEDIA_DOCUMENT_PLACEHOLDER,\n WS_HEARTBEAT_INTERVAL_MS,\n WS_MAX_RECONNECT_ATTEMPTS,\n WS_MAX_AUTH_FAILURE_ATTEMPTS,\n EVENT_ENTER_CHECK_UPDATE,\n CMD_ENTER_EVENT_REPLY,\n SCENE_WECOM_OPENCLAW,\n} from \"./const.js\";\nimport type { WeComMonitorOptions, MessageState } from \"./interface.js\";\nimport { parseMessageContent, type MessageBody } from \"./message-parser.js\";\nimport { sendWeComReply, StreamExpiredError } from \"./message-sender.js\";\nimport { downloadAndSaveImages, downloadAndSaveFiles } from \"./media-handler.js\";\nimport { uploadAndSendMedia } from \"./media-uploader.js\";\nimport { extractTemplateCards, maskTemplateCardBlocks } from \"./template-card-parser.js\";\nimport { checkGroupPolicy } from \"./group-policy.js\";\nimport { checkDmPolicy } from \"./dm-policy.js\";\nimport {\n setWeComWebSocket,\n setMessageState,\n deleteMessageState,\n setReqIdForChat,\n warmupReqIdStore,\n startMessageStateCleanup,\n stopMessageStateCleanup,\n cleanupAccount,\n} from \"./state-manager.js\";\n\nimport { PLUGIN_VERSION } from \"./version.js\";\n\n/**\n * 去除文本中的 `<think>...</think>` 标签(支持跨行),返回剩余可见文本。\n * 用于判断大模型回复中是否包含实际用户可见内容(而非仅有 thinking 推理过程)。\n */\nfunction stripThinkTags(text: string): string {\n return text;\n // return text.replace(/<think>[\\s\\S]*?<\\/think>/g, \"\").trim();\n}\n\n// ============================================================================\n// 模板卡片缓存与事件更新\n// ============================================================================\n\ninterface SentTemplateCardCacheEntry {\n templateCard: TemplateCard;\n createdAt: number;\n}\n\nconst sentTemplateCardByTaskId = new Map<string, SentTemplateCardCacheEntry>();\nconst TEMPLATE_CARD_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\nconst TEMPLATE_CARD_CACHE_MAX_SIZE = 300;\n\nfunction getTemplateCardCacheKey(accountId: string, taskId: string): string {\n return `${accountId}:${taskId}`;\n}\n\nfunction pruneTemplateCardCache(): void {\n const now = Date.now();\n\n for (const [key, entry] of sentTemplateCardByTaskId) {\n if (now - entry.createdAt >= TEMPLATE_CARD_CACHE_TTL_MS) {\n sentTemplateCardByTaskId.delete(key);\n }\n }\n\n if (sentTemplateCardByTaskId.size <= TEMPLATE_CARD_CACHE_MAX_SIZE) {\n return;\n }\n\n const sortedEntries = [...sentTemplateCardByTaskId.entries()].sort(\n (a, b) => a[1].createdAt - b[1].createdAt,\n );\n const removeCount = sentTemplateCardByTaskId.size - TEMPLATE_CARD_CACHE_MAX_SIZE;\n for (const [key] of sortedEntries.slice(0, removeCount)) {\n sentTemplateCardByTaskId.delete(key);\n }\n}\n\nfunction cloneTemplateCard(card: TemplateCard): TemplateCard {\n return JSON.parse(JSON.stringify(card)) as TemplateCard;\n}\n\nfunction saveTemplateCardToCache(params: {\n accountId: string;\n templateCard: TemplateCard;\n runtime: RuntimeEnv;\n}): void {\n const { accountId, templateCard, runtime } = params;\n const taskId = templateCard.task_id;\n if (!taskId) {\n runtime.log?.(\"[wecom][template-card] Skip cache: template card has no task_id\");\n return;\n }\n\n sentTemplateCardByTaskId.set(getTemplateCardCacheKey(accountId, taskId), {\n templateCard: cloneTemplateCard(templateCard),\n createdAt: Date.now(),\n });\n pruneTemplateCardCache();\n}\n\nfunction getTemplateCardFromCache(accountId: string, taskId: string): TemplateCard | undefined {\n pruneTemplateCardCache();\n const cached = sentTemplateCardByTaskId.get(getTemplateCardCacheKey(accountId, taskId));\n if (!cached) {\n return undefined;\n }\n return cloneTemplateCard(cached.templateCard);\n}\n\ntype TemplateCardEventPayload = NonNullable<NonNullable<MessageBody[\"event\"]>[\"template_card_event\"]>;\n\nfunction buildSelectedOptionMap(templateCardEvent?: TemplateCardEventPayload): Map<string, string[]> {\n const selectedMap = new Map<string, string[]>();\n const selectedItems = templateCardEvent?.selected_items?.selected_item ?? [];\n\n for (const item of selectedItems) {\n const questionKey = item.question_key?.trim();\n if (!questionKey) {\n continue;\n }\n const optionIds = item.option_ids?.option_id?.filter(Boolean) ?? [];\n selectedMap.set(questionKey, optionIds);\n }\n\n return selectedMap;\n}\n\nfunction applySelectedStateToTemplateCard(params: {\n templateCard: TemplateCard;\n selectedMap: Map<string, string[]>;\n templateCardEvent?: TemplateCardEventPayload;\n}): TemplateCard {\n const { templateCard, selectedMap, templateCardEvent } = params;\n const nextCard = cloneTemplateCard(templateCard);\n\n if (templateCardEvent?.task_id) {\n nextCard.task_id = templateCardEvent.task_id;\n }\n if (templateCardEvent?.card_type) {\n nextCard.card_type = templateCardEvent.card_type;\n }\n\n // 交互完成后将提交按钮文案更新为已提交,提升用户感知\n if (nextCard.submit_button?.text) {\n nextCard.submit_button.text = \"已提交\";\n }\n\n if (nextCard.checkbox?.question_key) {\n const selectedIds = selectedMap.get(nextCard.checkbox.question_key) ?? [];\n nextCard.checkbox.disable = true;\n if (Array.isArray(nextCard.checkbox.option_list)) {\n nextCard.checkbox.option_list = nextCard.checkbox.option_list.map((option) => ({\n ...option,\n is_checked: selectedIds.includes(option.id),\n }));\n }\n }\n\n if (Array.isArray(nextCard.select_list)) {\n nextCard.select_list = nextCard.select_list.map((selection) => {\n const selectedIds = selectedMap.get(selection.question_key) ?? [];\n return {\n ...selection,\n disable: true,\n selected_id: selectedIds[0] ?? selection.selected_id,\n };\n });\n }\n\n if (nextCard.button_selection?.question_key) {\n const selectedIds = selectedMap.get(nextCard.button_selection.question_key) ?? [];\n nextCard.button_selection.disable = true;\n if (selectedIds[0]) {\n nextCard.button_selection.selected_id = selectedIds[0];\n }\n }\n\n return nextCard;\n}\n\nasync function updateTemplateCardOnEvent(params: {\n frame: WsFrame;\n accountId: string;\n runtime: RuntimeEnv;\n wsClient: WSClient;\n}): Promise<void> {\n const { frame, accountId, runtime, wsClient } = params;\n const body = frame.body as MessageBody;\n const templateCardEvent = body.event?.template_card_event;\n const taskId = templateCardEvent?.task_id;\n\n if (!taskId) {\n runtime.log?.(`[${accountId}] [template-card-update] Skip update: missing task_id in callback`);\n return;\n }\n\n const cachedCard = getTemplateCardFromCache(accountId, taskId);\n if (!cachedCard) {\n runtime.log?.(`[${accountId}] [template-card-update] Skip update: task_id=${taskId} not found in cache`);\n return;\n }\n\n const selectedMap = buildSelectedOptionMap(templateCardEvent);\n const updatedCard = applySelectedStateToTemplateCard({\n templateCard: cachedCard,\n selectedMap,\n templateCardEvent,\n });\n\n await wsClient.updateTemplateCard(frame, updatedCard, [body.from.userid]);\n runtime.log?.(`[${accountId}] [template-card-update] Updated card by task_id=${taskId}`);\n\n // 将更新后的卡片写回缓存,后续多次点击时状态保持一致\n saveTemplateCardToCache({\n accountId,\n templateCard: updatedCard,\n runtime,\n });\n}\n\n// ============================================================================\n// 媒体本地路径白名单扩展\n// ============================================================================\n\n/**\n * 解析 openclaw 状态目录(与 plugin-sdk 内部逻辑保持一致)\n */\nfunction 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\n/**\n * 在 getDefaultMediaLocalRoots() 基础上,将 stateDir 本身也加入白名单,\n * 并合并用户在 WeComConfig 中配置的自定义 mediaLocalRoots。\n *\n * getDefaultMediaLocalRoots() 仅包含 stateDir 下的子目录(media/agents/workspace/sandboxes),\n * 但 agent 生成的文件可能直接放在 stateDir 根目录下(如 ~/.openclaw-dev/1.png),\n * 因此需要将 stateDir 本身也加入白名单以避免 LocalMediaAccessError。\n *\n * 用户可在 openclaw.json 中配置:\n * {\n * \"channels\": {\n * \"wecom\": {\n * \"mediaLocalRoots\": [\"~/Downloads\", \"~/Documents\"]\n * }\n * }\n * }\n */\nasync function getExtendedMediaLocalRoots(config?: WeComConfig): Promise<string[]> {\n // 从兼容层获取默认白名单(内部已处理低版本 SDK 的 fallback)\n const defaults = await getDefaultMediaLocalRoots();\n const roots: string[] = [...defaults];\n\n const stateDir = path.resolve(resolveStateDir());\n if (!roots.includes(stateDir)) {\n roots.push(stateDir);\n }\n // 合并用户在 WeComConfig 中配置的自定义路径\n if (config?.mediaLocalRoots) {\n for (const r of config.mediaLocalRoots) {\n const resolved = path.resolve(r.replace(/^~(?=\\/|$)/, os.homedir()));\n if (!roots.includes(resolved)) {\n roots.push(resolved);\n }\n }\n }\n return roots;\n}\n\n// ============================================================================\n// 媒体发送错误提示\n// ============================================================================\n\n/**\n * 根据媒体发送结果生成纯文本错误摘要(用于替换 thinking 流式消息展示给用户)。\n *\n * 使用纯文本而非 markdown 格式,因为 replyStream 只支持纯文本。\n */\nfunction buildMediaErrorSummary(\n mediaUrl: string,\n result: { rejectReason?: string; error?: string },\n): string {\n if (result.error?.includes(\"LocalMediaAccessError\")) {\n return `⚠️ 文件发送失败:没有权限访问路径 ${mediaUrl}\\n请在 openclaw.json 的 mediaLocalRoots 中添加该路径的父目录后重启生效。`;\n }\n if (result.rejectReason) {\n return `⚠️ 文件发送失败:${result.rejectReason}`;\n }\n return `⚠️ 文件发送失败:无法处理文件 ${mediaUrl},请稍后再试。`;\n}\n\n// ============================================================================\n// 重新导出(保持向后兼容)\n// ============================================================================\n\nexport type { WeComMonitorOptions } from \"./interface.js\";\nexport { WeComCommand } from \"./const.js\";\nexport {\n getWeComWebSocket,\n setReqIdForChat,\n getReqIdForChatAsync,\n getReqIdForChat,\n deleteReqIdForChat,\n warmupReqIdStore,\n flushReqIdStore,\n} from \"./state-manager.js\";\nexport { sendWeComReply } from \"./message-sender.js\";\n\n// ============================================================================\n// 消息上下文构建\n// ============================================================================\n\n/**\n * 构建消息上下文\n */\nfunction buildMessageContext(\n frame: WsFrame,\n account: ResolvedWeComAccount,\n config: OpenClawConfig,\n text: string,\n mediaList: Array<{ path: string; contentType?: string }>,\n quoteContent?: string\n) {\n const core = getWeComRuntime();\n const body = frame.body as MessageBody;\n const chatId = body.chatid || body.from.userid;\n const chatType = body.chattype === \"group\" ? \"group\" : \"direct\";\n\n // 解析路由信息\n const route = core.channel.routing.resolveAgentRoute({\n cfg: config,\n channel: CHANNEL_ID,\n accountId: account.accountId,\n peer: {\n kind: chatType,\n id: chatId,\n },\n });\n\n // 构建会话标签\n const fromLabel = chatType === \"group\" ? `group:${chatId}` : `user:${body.from.userid}`;\n\n // 当只有媒体没有文本时,使用占位符标识媒体类型\n const hasImages = mediaList.some((m) => m.contentType?.startsWith(\"image/\"));\n const messageBody = text || (mediaList.length > 0 ? (hasImages ? MEDIA_IMAGE_PLACEHOLDER : MEDIA_DOCUMENT_PLACEHOLDER) : \"\");\n\n // 构建多媒体数组\n const mediaPaths = mediaList.length > 0 ? mediaList.map((m) => m.path) : undefined;\n const mediaTypes = mediaList.length > 0\n ? (mediaList.map((m) => m.contentType).filter(Boolean) as string[])\n : undefined;\n\n // 构建标准消息上下文\n return core.channel.reply.finalizeInboundContext({\n Body: messageBody,\n RawBody: messageBody,\n CommandBody: messageBody,\n\n MessageSid: body.msgid,\n\n From: chatType === \"group\" ? `${CHANNEL_ID}:group:${chatId}` : `${CHANNEL_ID}:${body.from.userid}`,\n To: `${CHANNEL_ID}:${chatId}`,\n SenderId: body.from.userid,\n\n SessionKey: route.sessionKey,\n AccountId: account.accountId,\n\n ChatType: chatType,\n ConversationLabel: fromLabel,\n\n Timestamp: Date.now(),\n\n Provider: CHANNEL_ID,\n Surface: CHANNEL_ID,\n\n OriginatingChannel: CHANNEL_ID,\n OriginatingTo: `${CHANNEL_ID}:${chatId}`,\n\n CommandAuthorized: true,\n\n ResponseUrl: body.response_url,\n ReqId: frame.headers.req_id,\n WeComFrame: frame,\n\n MediaPath: mediaList[0]?.path,\n MediaType: mediaList[0]?.contentType,\n MediaPaths: mediaPaths,\n MediaTypes: mediaTypes,\n MediaUrls: mediaPaths,\n\n ReplyToBody: quoteContent,\n });\n}\n\n// ============================================================================\n// 消息处理和回复\n// ============================================================================\n\n/** deliver 回调所需的上下文 */\ninterface DeliverContext {\n wsClient: WSClient;\n frame: WsFrame;\n state: MessageState;\n account: ResolvedWeComAccount;\n runtime: RuntimeEnv;\n}\n\n/**\n * 发送\"思考中\"消息\n */\nasync function sendThinkingReply(params: {\n wsClient: WSClient;\n frame: WsFrame;\n streamId: string;\n runtime: RuntimeEnv;\n state?: MessageState;\n}): Promise<void> {\n const { wsClient, frame, streamId, runtime, state } = params;\n try {\n await sendWeComReply({\n wsClient,\n frame,\n text: THINKING_MESSAGE,\n runtime,\n finish: false,\n streamId,\n });\n } catch (err) {\n if (err instanceof StreamExpiredError && state) {\n state.streamExpired = true;\n runtime.log?.(`[wecom] Stream expired during thinking reply, will fallback to proactive send`);\n } else {\n runtime.error?.(`[wecom] Failed to send thinking message: ${String(err)}`);\n }\n }\n}\n\n/**\n * 上传并发送一批媒体文件(统一走主动发送通道)\n *\n * replyMedia(被动回复)无法覆盖 replyStream 发出的 thinking 流式消息,\n * 因此所有媒体统一走 aibot_send_msg 主动发送。\n */\nasync function sendMediaBatch(\n ctx: DeliverContext,\n mediaUrls: string[],\n): Promise<void> {\n const { wsClient, frame, state, account, runtime } = ctx;\n const body = frame.body as MessageBody;\n const chatId = body.chatid || body.from.userid;\n const mediaLocalRoots = await getExtendedMediaLocalRoots(account.config);\n\n runtime.log?.(`[wecom][debug] mediaLocalRoots=${JSON.stringify(mediaLocalRoots)}, mediaUrls=${JSON.stringify(mediaUrls)}`);\n\n for (const mediaUrl of mediaUrls) {\n const result = await uploadAndSendMedia({\n wsClient,\n mediaUrl,\n chatId,\n mediaLocalRoots,\n log: (...args: any[]) => runtime.log?.(...args),\n errorLog: (...args: any[]) => runtime.error?.(...args),\n });\n\n if (result.ok) {\n state.hasMedia = true;\n } else {\n state.hasMediaFailed = true;\n runtime.error?.(`[wecom] Media send failed: url=${mediaUrl}, reason=${result.rejectReason || result.error}`);\n // 收集错误摘要,后续在 finishThinkingStream 中直接替换 thinking 流展示给用户\n const summary = buildMediaErrorSummary(mediaUrl, result);\n state.mediaErrorSummary = state.mediaErrorSummary\n ? `${state.mediaErrorSummary}\\n\\n${summary}`\n : summary;\n }\n }\n}\n\n/**\n * 关闭 thinking 流(发送 finish=true 的流式消息)\n *\n * thinking 是通过 replyStream 用 streamId 发的流式消息,\n * 只有同一 streamId 的 replyStream(finish=true) 才能关闭它。\n *\n * ⚠️ 注意:企微会忽略空格等不可见内容,必须用有可见字符的文案才能真正\n * 替换掉 thinking 动画,否则 thinking 会一直残留。\n *\n * 关闭策略(按优先级):\n * 0. [新增] 有模板卡片代码块 → 提取卡片并主动发送,用剩余文本关闭流\n * 1. 有可见文本 → 用完整文本关闭\n * 2. 有媒体成功发送(通过 deliver 回调) → 用友好提示\"文件已发送\"\n * 3. 媒体发送失败 → 直接用错误摘要替换 thinking\n * 4. 其他 → 用通用\"处理完成\"提示\n * (agent 可能已通过内置 message 工具直接发送了文件,\n * 该路径走 outbound.sendMedia 完全绕过 deliver 回调,\n * 所以 state 中无记录,但文件已实际送达)\n *\n * 降级策略:\n * - 当 streamExpired=true(errcode 846608)时,流式通道已不可用(>6分钟),\n * 改用 wsClient.sendMessage 主动发送完整文本。\n */\nasync function finishThinkingStream(ctx: DeliverContext): Promise<void> {\n const { wsClient, frame, state, account, runtime } = ctx;\n const body = frame.body as MessageBody;\n const chatId = body.chatid || body.from.userid;\n const visibleText = stripThinkTags(state.accumulatedText);\n\n // ── 模板卡片检测与发送 ──────────────────────────────────────────────\n // 在确定 finishText 之前,先检查累积文本中是否包含模板卡片 JSON 代码块。\n // 若检测到合法卡片,通过 sendMessage 主动发送后,用剩余文本关闭流。\n if (visibleText) {\n runtime.log?.(`[wecom][template-card] finishThinkingStream: visibleText exists, length=${visibleText.length}, running extractTemplateCards...`);\n const logFn = (...args: any[]): void => {\n runtime.log?.(...args);\n };\n const { cards, remainingText } = extractTemplateCards(state.accumulatedText, logFn);\n\n runtime.log?.(`[wecom][template-card] finishThinkingStream: extractTemplateCards result — cards=${cards.length}, remainingTextLength=${remainingText.length}`);\n\n if (cards.length > 0) {\n runtime.log?.(`[wecom][template-card] finishThinkingStream: ${cards.length} card(s) detected, card_types=[${cards.map(c => c.cardType).join(\", \")}]`);\n await sendTemplateCards(ctx, cards);\n\n // 用剩余文本关闭流(可能为空)\n const trimmedRemaining = stripThinkTags(remainingText);\n const finishText = trimmedRemaining\n ? remainingText\n : (state.hasTemplateCard ? \"📋 卡片消息已发送。\" : \"\");\n runtime.log?.(`[wecom][template-card] finishThinkingStream: closing stream with finishText=\"${finishText.slice(0, 100)}...\", hasTemplateCard=${state.hasTemplateCard}`);\n await sendWeComReply({ wsClient, frame, text: finishText, runtime, finish: true, streamId: state.streamId });\n return;\n }\n } else {\n runtime.log?.(`[wecom][template-card] finishThinkingStream: no visibleText, skipping template card extraction`);\n }\n // ── 模板卡片检测结束 ────────────────────────────────────────────────\n\n let finishText: string = state.accumulatedText;\n if (visibleText) {\n // 有可见文本:用完整文本关闭流(覆盖 thinking 为真实内容)\n finishText = state.accumulatedText;\n } else if (state.hasMedia) {\n if (state.hasMediaFailed && state.mediaErrorSummary) {\n // 媒体成功发送:用友好提示告知用户\n finishText = finishText ? `${finishText}\\n\\n${state.mediaErrorSummary}` : state.mediaErrorSummary;\n } else if (!finishText) {\n finishText = \"📎 文件已发送,请查收。\";\n }\n }\n\n // if (!finishText) {\n // finishText = \"✅ 处理完成。\";\n // }\n\n if (finishText) {\n // 尝试流式发送;若已知过期或发送时发现过期,统一降级为主动发送\n let expired = state.streamExpired;\n if (!expired) {\n try {\n await sendWeComReply({wsClient, frame, text: finishText, runtime, finish: true, streamId: state.streamId});\n } catch (err) {\n if (err instanceof StreamExpiredError) {\n expired = true;\n } else {\n throw err;\n }\n }\n }\n if (expired) {\n runtime.log?.(`[wecom] Stream expired, sending final text via sendMessage (proactive)`);\n await wsClient.sendMessage(chatId, {\n msgtype: \"markdown\",\n markdown: { content: finishText },\n });\n }\n }\n}\n\n/**\n * 逐个发送已提取的模板卡片(通过 wsClient.sendMessage 主动推送)\n *\n * 发送失败不阻塞流程,仅记录错误日志。\n */\nasync function sendTemplateCards(\n ctx: DeliverContext,\n cards: import(\"./interface.js\").ExtractedTemplateCard[],\n): Promise<void> {\n const { wsClient, frame, state, runtime, account } = ctx;\n const body = frame.body as MessageBody;\n const chatId = body.chatid || body.from.userid;\n\n for (const card of cards) {\n try {\n runtime.log?.(`[wecom][template-card] Sending card_type=${card.cardType} to chatId=${chatId}`);\n\n const rawTemplateCard = card.cardJson as Record<string, unknown>;\n if (typeof rawTemplateCard.card_type !== \"string\") {\n runtime.error?.(\"[wecom][template-card] Skip sending invalid card: missing card_type\");\n continue;\n }\n\n const templateCard = rawTemplateCard as unknown as TemplateCard;\n await wsClient.sendMessage(chatId, {\n msgtype: \"template_card\",\n template_card: templateCard,\n });\n state.hasTemplateCard = true;\n saveTemplateCardToCache({\n accountId: account.accountId,\n templateCard,\n runtime,\n });\n runtime.log?.(`[wecom][template-card] Card sent successfully: card_type=${card.cardType}`);\n } catch (err) {\n runtime.error?.(`[wecom][template-card] Failed to send card: card_type=${card.cardType}, error=${JSON.stringify(err)}`);\n }\n }\n}\n\n/**\n * 路由消息到核心处理流程并处理回复\n */\nasync function routeAndDispatchMessage(params: {\n ctxPayload: ReturnType<typeof buildMessageContext>;\n config: OpenClawConfig;\n account: ResolvedWeComAccount;\n wsClient: WSClient;\n frame: WsFrame;\n state: MessageState;\n runtime: RuntimeEnv;\n onCleanup: () => void;\n}): Promise<void> {\n const { ctxPayload, config, account, wsClient, frame, state, runtime, onCleanup } = params;\n const core = getWeComRuntime();\n const ctx: DeliverContext = { wsClient, frame, state, account, runtime };\n\n // 防止 onCleanup 被多次调用(onError 回调与 catch 块可能重复触发)\n let cleanedUp = false;\n const safeCleanup = () => {\n if (!cleanedUp) { cleanedUp = true; onCleanup(); }\n };\n\n let isShowThink = !(account.sendThinkingMessage ?? true);\n\n try {\n await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({\n ctx: ctxPayload,\n cfg: config,\n // replyResolver: async (ctx, opts) => {\n // const startTime = Date.now();\n // const TEN_MINUTES = 7 * 60 * 1000;\n //\n // console.log('开始输出内容');\n //\n // while (Date.now() - startTime < TEN_MINUTES) {\n // // 每隔一段时间发送一个 block reply\n // await opts?.onBlockReply?.({ text: `输出内容 ${new Date().toISOString()}` });\n // await new Promise(resolve => setTimeout(resolve, 30 * 1000)); // 每5秒输出一次\n // }\n //\n // return { text: \"10分钟输出完成\" }; // 最终回复\n // },\n dispatcherOptions: {\n onReplyStart: async () => {\n if (!isShowThink && state.streamId && !state.accumulatedText) {\n try {\n await sendThinkingReply({wsClient, frame, streamId: state.streamId, runtime, state})\n } catch (e) {\n runtime.error?.(`[wecom] sendThinkingReply threw err: ${String(e)}`);\n }\n isShowThink = true;\n }\n },\n deliver: async (payload, info) => {\n state.deliverCalled = true;\n // runtime.log?.(`[openclaw -> plugin] kind=${info.kind}, text=${payload.text ?? ''}, mediaUrl=${payload.mediaUrl ?? ''}, mediaUrls=${JSON.stringify(payload.mediaUrls ?? [])}`);\n\n // 累积文本\n if (payload.text) {\n state.accumulatedText += (payload.text || '');\n }\n\n // 发送媒体(统一走主动发送)\n const mediaUrls = payload.mediaUrls?.length ? payload.mediaUrls : payload.mediaUrl ? [payload.mediaUrl] : [];\n if (mediaUrls.length > 0) {\n try {\n await sendMediaBatch(ctx, mediaUrls);\n } catch (mediaErr) {\n // sendMediaBatch 内部异常(如 getDefaultMediaLocalRoots 不可用等)\n // 必须标记 state,否则 finishThinkingStream 会显示\"处理完成\"误导用户\n state.hasMediaFailed = true;\n const errMsg = String(mediaErr);\n const summary = `⚠️ 文件发送失败:内部处理异常,请升级 openclaw 到最新版本后重试。\\n错误详情:${errMsg}`;\n state.mediaErrorSummary = state.mediaErrorSummary\n ? `${state.mediaErrorSummary}\\n\\n${summary}`\n : summary;\n runtime.error?.(`[wecom] sendMediaBatch threw: ${errMsg}`);\n }\n }\n\n // 中间帧:有可见文本时流式更新(流式过期后跳过,等 deliver 完成后主动发送)\n // 使用 maskTemplateCardBlocks 遮罩正在构建中的模板卡片代码块,\n // 避免 JSON 源码在流式输出过程中暴露给终端用户\n if (info.kind !== \"final\" && state.accumulatedText && !state.streamExpired) {\n try {\n const displayText = maskTemplateCardBlocks(state.accumulatedText, (...args: any[]) => runtime.log?.(...args));\n if (displayText !== state.accumulatedText) {\n runtime.log?.(`[wecom][template-card] Mid-frame masked: original=${state.accumulatedText.length}chars, masked=${displayText.length}chars`);\n }\n await sendWeComReply({ wsClient, frame, text: displayText, runtime, finish: false, streamId: state.streamId });\n } catch (err) {\n if (err instanceof StreamExpiredError) {\n state.streamExpired = true;\n runtime.log?.(`[wecom] Stream expired during intermediate reply, will fallback to proactive send`);\n } else {\n throw err;\n }\n }\n }\n },\n onError: (err, info) => {\n runtime.error?.(`[wecom] ${info.kind} reply failed: ${String(err)}`);\n },\n },\n });\n\n // 关闭 thinking 流\n await finishThinkingStream(ctx);\n safeCleanup();\n } catch (err) {\n runtime.error?.(`[wecom][plugin] Failed to process message: ${String(err)}`);\n // 即使 dispatch 抛异常,也需要关闭 thinking 流,\n // 避免 deliver 已成功发送媒体但后续出错时 thinking 消息残留或被错误文案覆盖\n try {\n await finishThinkingStream(ctx);\n } catch (finishErr) {\n runtime.error?.(`[wecom] Failed to finish thinking stream after dispatch error: ${String(finishErr)}`);\n }\n safeCleanup();\n }\n}\n\n/**\n * 处理企业微信消息(主函数)\n *\n * 处理流程:\n * 1. 解析消息内容(文本、图片、引用)\n * 2. 群组策略检查(仅群聊)\n * 3. DM Policy 访问控制检查(仅私聊)\n * 4. 下载并保存图片\n * 5. 初始化消息状态\n * 6. 发送\"思考中\"消息\n * 7. 路由消息到核心处理流程\n *\n * 整体带超时保护,防止单条消息处理阻塞过久\n */\nasync function processWeComMessage(params: {\n frame: WsFrame;\n account: ResolvedWeComAccount;\n config: OpenClawConfig;\n runtime: RuntimeEnv;\n wsClient: WSClient;\n}): Promise<void> {\n const { frame, account, config, runtime, wsClient } = params;\n const body = frame.body as MessageBody;\n const chatId = body.chatid || body.from.userid;\n const chatType = body.chattype === \"group\" ? \"group\" : \"direct\";\n const messageId = body.msgid;\n const reqId = frame.headers.req_id;\n\n // Step 1: 解析消息内容\n const { textParts, imageUrls, imageAesKeys, fileUrls, fileAesKeys, quoteContent } = parseMessageContent(body);\n let text = textParts.join(\"\\n\").trim();\n\n // // 群聊中移除 @机器人 的提及标记\n // if (body.chattype === \"group\") {\n // text = text.replace(/@\\S+/g, \"\").trim();\n // }\n\n // 如果文本为空但存在引用消息,使用引用消息内容\n if (!text && quoteContent) {\n text = quoteContent;\n runtime.log?.(\"[wecom][plugin] Using quote content as message body (user only mentioned bot)\");\n }\n\n // 如果既没有文本也没有图片也没有文件也没有引用内容,则跳过\n if (!text && imageUrls.length === 0 && fileUrls.length === 0) {\n runtime.log?.(\"[wecom][plugin] Skipping empty message (no text, image, file or quote)\");\n return;\n }\n\n // Step 2: 群组策略检查(仅群聊)\n if (chatType === \"group\") {\n const groupPolicyResult = checkGroupPolicy({\n chatId,\n senderId: body.from.userid,\n account,\n config,\n runtime,\n });\n\n if (!groupPolicyResult.allowed) {\n return;\n }\n }\n\n // Step 3: DM Policy 访问控制检查(仅私聊)\n const dmPolicyResult = await checkDmPolicy({\n senderId: body.from.userid,\n isGroup: chatType === \"group\",\n account,\n wsClient,\n frame,\n runtime,\n });\n\n if (!dmPolicyResult.allowed) {\n return;\n }\n\n // Step 4: 下载并保存图片和文件\n const [imageMediaList, fileMediaList] = await Promise.all([\n downloadAndSaveImages({\n imageUrls,\n imageAesKeys,\n account,\n config,\n runtime,\n wsClient,\n }),\n downloadAndSaveFiles({\n fileUrls,\n fileAesKeys,\n account,\n config,\n runtime,\n wsClient,\n }),\n ]);\n const mediaList = [...imageMediaList, ...fileMediaList];\n\n // Step 5: 初始化消息状态\n setReqIdForChat(chatId, reqId, account.accountId);\n\n const streamId = generateReqId(\"stream\");\n const state: MessageState = { accumulatedText: \"\", streamId };\n setMessageState(messageId, state);\n\n const cleanupState = () => {\n deleteMessageState(messageId);\n };\n\n // // Step 6: 发送\"思考中\"消息\n // const shouldSendThinking = account.sendThinkingMessage ?? true;\n // if (shouldSendThinking) {\n // await sendThinkingReply({ wsClient, frame, streamId, runtime });\n // }\n\n // Step 7: 构建上下文并路由到核心处理流程(带整体超时保护)\n const ctxPayload = buildMessageContext(frame, account, config, text, mediaList, quoteContent);\n // runtime.log?.(`[plugin -> openclaw] body=${text}, mediaPaths=${JSON.stringify(mediaList.map(m => m.path))}${quoteContent ? `, quote=${quoteContent}` : ''}`);\n\n try {\n await routeAndDispatchMessage({\n ctxPayload,\n config,\n account,\n wsClient,\n frame,\n state,\n runtime,\n onCleanup: cleanupState,\n });\n } catch (err) {\n runtime.error?.(`[wecom][plugin] Message processing failed: ${String(err)}`);\n cleanupState();\n }\n}\n\n// ============================================================================\n// 创建 SDK Logger 适配器\n// ============================================================================\n\n/**\n * 创建适配 RuntimeEnv 的 Logger\n */\nfunction createSdkLogger(runtime: RuntimeEnv, accountId: string): Logger {\n return {\n debug: (message: string, ...args: any[]) => {\n runtime.log?.(`[${accountId}] ${message}`, ...args);\n },\n info: (message: string, ...args: any[]) => {\n runtime.log?.(`[${accountId}] ${message}`, ...args);\n },\n warn: (message: string, ...args: any[]) => {\n runtime.log?.(`[${accountId}] WARN: ${message}`, ...args);\n },\n error: (message: string, ...args: any[]) => {\n runtime.error?.(`[${accountId}] ${message}`, ...args);\n },\n };\n}\n\n// ============================================================================\n// 主函数\n// ============================================================================\n\n/**\n * 监听企业微信 WebSocket 连接\n * 使用 aibot-node-sdk 简化连接管理\n */\nexport async function monitorWeComProvider(options: WeComMonitorOptions): Promise<void> {\n const { account, config, runtime, abortSignal, setStatus } = options;\n\n runtime.log?.(`[${account.accountId}] [${PLUGIN_VERSION}] Initializing WSClient with SDK...`);\n runtime.error?.(`[${account.accountId}] [diag] monitor boot marker: build=20260325-event-debug-1`);\n\n // 启动消息状态定期清理\n startMessageStateCleanup();\n\n return new Promise((resolve, reject) => {\n const logger = createSdkLogger(runtime, account.accountId);\n\n const wsClient = new WSClient({\n botId: account.botId,\n secret: account.secret,\n wsUrl: account.websocketUrl,\n logger,\n heartbeatInterval: WS_HEARTBEAT_INTERVAL_MS,\n maxReconnectAttempts: WS_MAX_RECONNECT_ATTEMPTS,\n maxAuthFailureAttempts: WS_MAX_AUTH_FAILURE_ATTEMPTS,\n scene: SCENE_WECOM_OPENCLAW,\n plug_version: PLUGIN_VERSION,\n });\n\n // 防止 cleanup 被多次调用(abort handler、error handler、disconnected_event 可能竞态触发)\n let cleanedUp = false;\n\n // 清理函数:确保所有资源被释放(幂等)\n const cleanup = async () => {\n if (cleanedUp) return;\n cleanedUp = true;\n stopMessageStateCleanup();\n await cleanupAccount(account.accountId);\n };\n\n // 处理中止信号(框架 stopChannel 会触发 abort)\n // resolve() 让 Promise settle → 框架清理 store.tasks/store.aborts\n if (abortSignal) {\n abortSignal.addEventListener(\"abort\", async () => {\n runtime.log?.(`[${account.accountId}] Connection aborted`);\n wsClient.disconnect();\n await cleanup();\n resolve();\n });\n }\n\n // 监听连接事件\n wsClient.on(\"connected\", () => {\n runtime.log?.(`[${account.accountId}] WebSocket connected`);\n });\n\n // 监听认证成功事件\n wsClient.on(\"authenticated\", () => {\n runtime.log?.(`[${account.accountId}] Authentication successful`);\n setWeComWebSocket(account.accountId, wsClient);\n });\n\n // 监听断开事件\n wsClient.on(\"disconnected\", (reason) => {\n runtime.log?.(`[${account.accountId}] WebSocket disconnected: ${reason}`);\n });\n\n\n // 监听被踢下线事件(服务端因新连接建立而主动断开旧连接)\n //\n // SDK 内部已设置 isManualClose=true 阻止 SDK 层自动重连,连接不会自行恢复。\n // **不 reject/resolve Promise**——保持 pending 以阻止框架层 auto-restart。\n //\n // 为什么不能 reject/resolve:\n // - reject → 框架 auto-restart 介入 → 新连接建立 → 又被踢 → 两个实例互踢无限循环\n // - resolve → 同上,框架 .then() 中的 auto-restart 也会触发\n //\n // Promise pending 的安全性:\n // - store.tasks.has(id) = true → 阻止 Health Monitor 直接 startChannel(startChannel 检查 tasks.has)\n // - 框架 stopChannel → abort() → abort handler 中 resolve() → tasks 正常清理\n // - 用户修改配置 → config reload → stopChannel + startChannel → 正常恢复\n //\n // 显式调用 wsClient.disconnect() 确保 SDK 内部资源(定时器、队列等)完全释放。\n wsClient.on(\"event.disconnected_event\", async () => {\n const errorMsg = `Kicked by server: a new connection was established elsewhere. Auto-restart is suppressed to avoid mutual kicking. Please check for duplicate instances.`;\n runtime.error?.(`[${account.accountId}] ${errorMsg}`);\n wsClient.disconnect();\n await cleanup();\n setStatus?.({\n accountId: account.accountId,\n running: false,\n lastError: errorMsg,\n lastStopAt: Date.now(),\n });\n // Promise 保持 pending,不触发 auto-restart\n });\n\n // 监听重连事件\n wsClient.on(\"reconnecting\", (attempt) => {\n runtime.log?.(`[${account.accountId}] Reconnecting attempt ${attempt}...`);\n });\n\n // 监听错误事件\n wsClient.on(\"error\", async (error) => {\n runtime.error?.(`[${account.accountId}] WebSocket error: ${error.message}`);\n\n if (error instanceof WSAuthFailureError) {\n // 认证失败重试次数用尽(SDK 层已重试 WS_MAX_AUTH_FAILURE_ATTEMPTS 次)。\n // 配置错误(如 botId/secret 无效),框架 auto-restart 也无法恢复。\n //\n // **不 reject/resolve Promise**——保持 pending 以阻止框架层 auto-restart。\n //\n // 为什么不能 reject/resolve:\n // - reject/resolve → 框架 auto-restart(最多 10 次)× SDK 重试(5 次)= 60 次无意义尝试\n // - 且 Health Monitor 每小时还会 resetRestartAttempts 再来一轮\n //\n // Promise pending 的安全性:同被踢下线场景\n // - store.tasks.has(id) = true → 阻止 Health Monitor 直接 startChannel\n // - 框架 stopChannel / config reload → abort handler 中 resolve() → 正常清理\n // - 用户修改配置后框架通过 reload 机制重新启动\n const errorMsg = `Auth failure attempts exhausted (${WS_MAX_AUTH_FAILURE_ATTEMPTS} attempts). Please check botId/secret configuration.`;\n runtime.error?.(`[${account.accountId}] ${errorMsg}`);\n wsClient.disconnect();\n await cleanup();\n setStatus?.({\n accountId: account.accountId,\n running: false,\n lastError: errorMsg,\n lastStopAt: Date.now(),\n });\n return;\n }\n\n if (error instanceof WSReconnectExhaustedError) {\n // 网络断线重连次数用尽(SDK 层已重试 WS_MAX_RECONNECT_ATTEMPTS 次)。\n // 通常是网络/服务端问题,框架 auto-restart 可能恢复。\n //\n // reject Promise → 框架 auto-restart 介入(最多 MAX_RESTART_ATTEMPTS=10 次)\n // 总连接尝试次数 = (1 首次 + WS_MAX_RECONNECT_ATTEMPTS 重连) × (1 首轮 + 10 auto-restart)\n // = 11 × 11 = 121 次\n //\n // 如果 Health Monitor 介入(每 5 分钟检查),会 resetRestartAttempts 重新计数,\n // 受限于 DEFAULT_MAX_RESTARTS_PER_HOUR=10,每小时最多额外 10 × 121 = 1210 次。\n // 但因网络断线通常是暂时性的,auto-restart + Health Monitor 的兜底机制是合理的。\n //\n // 显式调用 wsClient.disconnect() 确保 SDK 内部资源完全释放,\n // 避免旧实例的定时器/队列残留。\n wsClient.disconnect();\n cleanup().finally(() => reject(error));\n return;\n }\n });\n\n // 监听版本检查事件:收到 enter_check_update 时回复当前插件版本\n wsClient.on(EVENT_ENTER_CHECK_UPDATE as any, async (frame: WsFrame) => {\n try {\n // runtime.log?.(`[${account.accountId}] Received enter_check_update, replying with version=${PLUGIN_VERSION}`);\n await wsClient.reply(frame, { version: PLUGIN_VERSION }, CMD_ENTER_EVENT_REPLY);\n } catch (err) {\n // runtime.error?.(`[${account.accountId}] Failed to reply enter_check_update: ${String(err)}`);\n }\n });\n\n // 监听普通消息\n wsClient.on(\"message\", async (frame: WsFrame) => {\n try {\n await processWeComMessage({\n frame,\n account,\n config,\n runtime,\n wsClient,\n });\n } catch (err) {\n runtime.error?.(`[${account.accountId}] Failed to process message: ${String(err)}`);\n }\n });\n\n // 监听所有事件回调(aibot_event_callback)。\n // 这里使用通用 event 监听,再按 eventtype 分发,兼容不同 SDK 版本在细分事件名上的差异。\n wsClient.on(\"event\", async (frame: WsFrame) => {\n try {\n const eventBody = frame.body as MessageBody;\n const eventType = eventBody.event?.eventtype;\n runtime.log?.(\n `[${account.accountId}] Received event callback: eventtype=${eventType ?? \"\"}, msgid=${eventBody.msgid ?? \"\"}`,\n );\n runtime.error?.(\n `[${account.accountId}] [diag] event-listener fired: eventtype=${eventType ?? \"\"}, msgid=${eventBody.msgid ?? \"\"}`,\n );\n\n if (eventType !== \"template_card_event\") {\n return;\n }\n\n const templateCardEvent = eventBody.event?.template_card_event;\n runtime.log?.(\n `[${account.accountId}] Received template_card_event: event_key=${templateCardEvent?.event_key ?? \"\"}, task_id=${templateCardEvent?.task_id ?? \"\"}`,\n );\n\n try {\n await updateTemplateCardOnEvent({\n frame,\n accountId: account.accountId,\n runtime,\n wsClient,\n });\n } catch (updateErr) {\n runtime.error?.(\n `[${account.accountId}] [template-card-update] Failed to update template card: ${String(updateErr)}`,\n );\n }\n\n await processWeComMessage({\n frame,\n account,\n config,\n runtime,\n wsClient,\n });\n } catch (err) {\n runtime.error?.(`[${account.accountId}] Failed to process template_card_event: ${String(err)}`);\n }\n });\n\n runtime.log?.(`[${account.accountId}] Event listeners attached: message + event(template_card_event)`);\n runtime.error?.(`[${account.accountId}] [diag] listeners-ready marker`);\n\n // 启动前预热 reqId 缓存,确保完成后再建立连接,避免 getSync 在预热完成前返回 undefined\n warmupReqIdStore(account.accountId, (...args) => runtime.log?.(...args))\n .then((count) => {\n runtime.log?.(`[${account.accountId}] Warmed up ${count} reqId entries from disk`);\n })\n .catch((err) => {\n runtime.error?.(`[${account.accountId}] Failed to warmup reqId store: ${String(err)}`);\n })\n .finally(() => {\n // 无论预热成功或失败,都建立连接\n wsClient.connect();\n });\n });\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"onboarding.js","sources":["../../../../../src/onboarding.ts"],"sourcesContent":["/**\n * 企业微信 setupWizard — 声明式 CLI setup wizard 配置。\n *\n * 框架通过 plugin.setupWizard 字段识别并驱动 channel 的引导配置流程。\n */\n\nimport type { ChannelSetupWizard, ChannelSetupDmPolicy } from \"openclaw/plugin-sdk/setup\";\nimport type { ChannelSetupAdapter } from \"openclaw/plugin-sdk/setup\";\nimport type { OpenClawConfig } from \"openclaw/plugin-sdk/core\";\nimport { addWildcardAllowFrom } from \"./openclaw-compat.js\";\nimport type { WeComConfig } from \"./utils.js\";\nimport { resolveWeComAccount, setWeComAccount } from \"./utils.js\";\nimport { CHANNEL_ID } from \"./const.js\";\n\n// ============================================================================\n// ChannelSetupAdapter — 框架用于应用配置输入的适配器\n// ============================================================================\n\nexport const wecomSetupAdapter: ChannelSetupAdapter = {\n applyAccountConfig: ({ cfg, input }) => {\n const patch: Partial<WeComConfig> = {};\n\n if (input.token !== undefined) {\n patch.botId = String(input.token).trim();\n }\n if (input.privateKey !== undefined) {\n patch.secret = String(input.privateKey).trim();\n }\n\n // 如果是首次配置,默认启用\n const account = resolveWeComAccount(cfg);\n if (!account.botId && !account.secret) {\n patch.enabled = true;\n }\n\n return setWeComAccount(cfg, patch);\n },\n};\n\n// ============================================================================\n// DM Policy 配置\n// ============================================================================\n\n/**\n * 设置企业微信 dmPolicy\n */\nfunction setWeComDmPolicy(\n cfg: OpenClawConfig,\n dmPolicy: \"pairing\" | \"allowlist\" | \"open\" | \"disabled\",\n): OpenClawConfig {\n const account = resolveWeComAccount(cfg);\n const existingAllowFrom = account.config.allowFrom ?? [];\n const allowFrom =\n dmPolicy === \"open\"\n ? addWildcardAllowFrom(existingAllowFrom.map((x) => String(x)))\n : existingAllowFrom.map((x) => String(x));\n\n return setWeComAccount(cfg, {\n dmPolicy,\n allowFrom,\n });\n}\n\nconst dmPolicy: ChannelSetupDmPolicy = {\n label: \"企业微信\",\n channel: CHANNEL_ID,\n policyKey: `channels.${CHANNEL_ID}.dmPolicy`,\n allowFromKey: `channels.${CHANNEL_ID}.allowFrom`,\n getCurrent: (cfg) => {\n const account = resolveWeComAccount(cfg);\n return account.config.dmPolicy ?? \"open\";\n },\n setPolicy: (cfg, policy) => {\n return setWeComDmPolicy(cfg, policy);\n },\n promptAllowFrom: async ({ cfg, prompter }) => {\n const account = resolveWeComAccount(cfg);\n const existingAllowFrom = account.config.allowFrom ?? [];\n\n const entry = await prompter.text({\n message: \"企业微信允许来源(用户ID或群组ID,逗号分隔)\",\n placeholder: \"user123, group456\",\n initialValue: existingAllowFrom[0] ? String(existingAllowFrom[0]) : undefined,\n });\n\n const allowFrom = String(entry ?? \"\")\n .split(/[\\n,;]+/g)\n .map((s) => s.trim())\n .filter(Boolean);\n\n return setWeComAccount(cfg, { allowFrom });\n },\n};\n\n// ============================================================================\n// ChannelSetupWizard — 声明式 setup wizard 配置\n// ============================================================================\n\nexport const wecomSetupWizard: ChannelSetupWizard = {\n channel: CHANNEL_ID,\n\n // ── 状态 ──────────────────────────────────────────────────────────────\n status: {\n configuredLabel: \"已配置 ✓\",\n unconfiguredLabel: \"需要 Bot ID 和 Secret\",\n configuredHint: \"已配置\",\n unconfiguredHint: \"需要设置\",\n resolveConfigured: ({ cfg }) => {\n const account = resolveWeComAccount(cfg);\n return Boolean(account.botId?.trim() && account.secret?.trim());\n },\n resolveStatusLines: ({ cfg, configured }) => {\n return [`企业微信: ${configured ? \"已配置\" : \"需要 Bot ID 和 Secret\"}`];\n },\n },\n\n // ── 引导说明 ──────────────────────────────────────────────────────────\n introNote: {\n title: \"企业微信设置\",\n lines: [\n \"企业微信机器人需要以下配置信息:\",\n \"1. Bot ID: 企业微信机器人 ID\",\n \"2. Secret: 企业微信机器人密钥\",\n ],\n shouldShow: ({ cfg }) => {\n const account = resolveWeComAccount(cfg);\n return !account.botId?.trim() || !account.secret?.trim();\n },\n },\n\n // ── 凭据输入 ──────────────────────────────────────────────────────────\n credentials: [\n {\n inputKey: \"token\",\n providerHint: \"企业微信\",\n credentialLabel: \"Bot ID\",\n envPrompt: \"使用环境变量中的 Bot ID?\",\n keepPrompt: \"Bot ID 已配置,保留当前值?\",\n inputPrompt: \"企业微信机器人 Bot ID\",\n inspect: ({ cfg }) => {\n const account = resolveWeComAccount(cfg);\n const hasValue = Boolean(account.botId?.trim());\n return {\n accountConfigured: hasValue,\n hasConfiguredValue: hasValue,\n resolvedValue: account.botId || undefined,\n };\n },\n applySet: ({ cfg, resolvedValue }) => {\n return setWeComAccount(cfg, { botId: resolvedValue });\n },\n },\n {\n inputKey: \"privateKey\",\n providerHint: \"企业微信\",\n credentialLabel: \"Secret\",\n envPrompt: \"使用环境变量中的 Secret?\",\n keepPrompt: \"Secret 已配置,保留当前值?\",\n inputPrompt: \"企业微信机器人 Secret\",\n inspect: ({ cfg }) => {\n const account = resolveWeComAccount(cfg);\n const hasValue = Boolean(account.secret?.trim());\n return {\n accountConfigured: hasValue,\n hasConfiguredValue: hasValue,\n resolvedValue: account.secret || undefined,\n };\n },\n applySet: ({ cfg, resolvedValue }) => {\n return setWeComAccount(cfg, { secret: resolvedValue });\n },\n },\n ],\n\n // ── 完成后的最终处理 ──────────────────────────────────────────────────\n finalize: async ({ cfg }) => {\n // 确保配置完成后 channel 处于启用状态\n const account = resolveWeComAccount(cfg);\n if (account.botId?.trim() && account.secret?.trim() && !account.enabled) {\n return { cfg: setWeComAccount(cfg, { enabled: true }) };\n }\n return undefined;\n },\n\n // ── 完成提示 ──────────────────────────────────────────────────────────\n completionNote: {\n title: \"企业微信配置完成\",\n lines: [\n \"企业微信机器人已配置完成。\",\n \"运行 `openclaw start` 启动服务。\",\n ],\n shouldShow: ({ cfg }) => {\n const account = resolveWeComAccount(cfg);\n return Boolean(account.botId?.trim() && account.secret?.trim());\n },\n },\n\n // ── DM 策略 ──────────────────────────────────────────────────────────\n dmPolicy,\n\n // ── 禁用 ─────────────────────────────────────────────────────────────\n disable: (cfg) => {\n return setWeComAccount(cfg, { enabled: false });\n },\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
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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"}
|
|
@@ -1 +0,0 @@
|
|
|
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":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
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;;;;"}
|
|
@@ -1 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
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":";;;AAAA;;;;;AAKG;AAWH;AACA;AACA;AAEA;AACA,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAoB;AAErD;;AAEG;AACG,SAAU,iBAAiB,CAAC,SAAiB,EAAA;IACjD,OAAO,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI;AACjD;AAEA;;AAEG;AACG,SAAU,iBAAiB,CAAC,SAAiB,EAAE,MAAgB,EAAA;AACnE,IAAA,iBAAiB,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC;AAC1C;AAmBA;AACA,MAAM,aAAa,GAAG,IAAI,GAAG,EAA6B;AAE1D;AACA,IAAI,YAAY,GAA0C,IAAI;AAE9D;;AAEG;SACa,wBAAwB,GAAA;AACtC,IAAA,IAAI,YAAY;QAAE;AAElB,IAAA,YAAY,GAAG,WAAW,CAAC,MAAK;AAC9B,QAAA,kBAAkB,EAAE;IACtB,CAAC,EAAE,iCAAiC,CAAC;;IAGrC,IAAI,YAAY,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,OAAO,IAAI,YAAY,EAAE;QAC/E,YAAY,CAAC,KAAK,EAAE;IACtB;AACF;AAEA;;AAEG;SACa,uBAAuB,GAAA;IACrC,IAAI,YAAY,EAAE;QAChB,aAAa,CAAC,YAAY,CAAC;QAC3B,YAAY,GAAG,IAAI;IACrB;AACF;AAEA;;AAEG;AACH,SAAS,kBAAkB,GAAA;AACzB,IAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;;IAGtB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,aAAa,EAAE;QACxC,IAAI,GAAG,GAAG,KAAK,CAAC,SAAS,IAAI,oBAAoB,EAAE;AACjD,YAAA,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC;QAC3B;IACF;;AAGA,IAAA,IAAI,aAAa,CAAC,IAAI,GAAG,sBAAsB,EAAE;AAC/C,QAAA,MAAM,MAAM,GAAG,CAAC,GAAG,aAAa,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3F,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,sBAAsB,CAAC;AAC7E,QAAA,KAAK,MAAM,CAAC,GAAG,CAAC,IAAI,QAAQ,EAAE;AAC5B,YAAA,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC;QAC3B;IACF;AACF;AAEA;;AAEG;AACG,SAAU,eAAe,CAAC,SAAiB,EAAE,KAAmB,EAAA;AACpE,IAAA,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE;QAC3B,KAAK;AACL,QAAA,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;AACtB,KAAA,CAAC;AACJ;AAiBA;;AAEG;AACG,SAAU,kBAAkB,CAAC,SAAiB,EAAA;AAClD,IAAA,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC;AACjC;AASA;AACA;AACA;AAEA;;;;AAIG;AACH,MAAM,WAAW,GAAG,IAAI,GAAG,EAAgC;AAE3D,SAAS,qBAAqB,CAAC,SAAiB,EAAA;IAC9C,IAAI,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC;IACtC,IAAI,CAAC,KAAK,EAAE;AACV,QAAA,KAAK,GAAG,0BAA0B,CAAU,CAAC;AAC7C,QAAA,WAAW,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC;IACnC;AACA,IAAA,OAAO,KAAK;AACd;AAEA;AACA;AACA;AAEA;;AAEG;AACG,SAAU,eAAe,CAAC,MAAc,EAAE,KAAa,EAAE,SAAS,GAAG,SAAS,EAAA;IAClF,qBAAqB,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC;AACrD;AAuBA;;;;AAIG;AACI,eAAe,gBAAgB,CACpC,SAAS,GAAG,SAAS,EACrB,GAAkC,EAAA;;AAGlC,IAAA,GAAG,GAAG,0DAA0D,CAAC;AACjE,IAAA,OAAO,CAAC;AACV;AAWA;AACA;AACA;AAEA;;AAEG;AACI,eAAe,cAAc,CAAC,SAAiB,EAAA;;IAEpD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC;IACjD,IAAI,QAAQ,EAAE;AACZ,QAAA,IAAI;YACF,QAAQ,CAAC,UAAU,EAAE;QACvB;AAAE,QAAA,MAAM;;QAER;AACA,QAAA,iBAAiB,CAAC,MAAM,CAAC,SAAS,CAAC;IACrC;;;AAIF;;;;"}
|