OpenClaw 完整备份 - 2026-03-21
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
|
||||
import { OpenClawPluginApi } from 'openclaw/plugin-sdk';
|
||||
|
||||
declare const plugin: {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
configSchema: any;
|
||||
register(api: OpenClawPluginApi): void;
|
||||
};
|
||||
|
||||
export { plugin as default };
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
import { type ChannelPlugin } from "openclaw/plugin-sdk";
|
||||
import type { ResolvedWeComAccount } from "./utils.js";
|
||||
export declare const wecomPlugin: ChannelPlugin<ResolvedWeComAccount>;
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 企业微信渠道常量定义
|
||||
*/
|
||||
/**
|
||||
* 企业微信渠道 ID
|
||||
*/
|
||||
export declare const CHANNEL_ID: "wecom";
|
||||
/**
|
||||
* 企业微信 WebSocket 命令枚举
|
||||
*/
|
||||
export declare enum WeComCommand {
|
||||
/** 认证订阅 */
|
||||
SUBSCRIBE = "aibot_subscribe",
|
||||
/** 心跳 */
|
||||
PING = "ping",
|
||||
/** 企业微信推送消息 */
|
||||
AIBOT_CALLBACK = "aibot_callback",
|
||||
/** clawdbot 响应消息 */
|
||||
AIBOT_RESPONSE = "aibot_response"
|
||||
}
|
||||
/** 图片下载超时时间(毫秒) */
|
||||
export declare const IMAGE_DOWNLOAD_TIMEOUT_MS = 30000;
|
||||
/** 文件下载超时时间(毫秒) */
|
||||
export declare const FILE_DOWNLOAD_TIMEOUT_MS = 60000;
|
||||
/** 消息发送超时时间(毫秒) */
|
||||
export declare const REPLY_SEND_TIMEOUT_MS = 15000;
|
||||
/** 消息处理总超时时间(毫秒) */
|
||||
export declare const MESSAGE_PROCESS_TIMEOUT_MS: number;
|
||||
/** WebSocket 心跳间隔(毫秒) */
|
||||
export declare const WS_HEARTBEAT_INTERVAL_MS = 30000;
|
||||
/** WebSocket 最大重连次数 */
|
||||
export declare const WS_MAX_RECONNECT_ATTEMPTS = 100;
|
||||
/** messageStates Map 条目的最大 TTL(毫秒),防止内存泄漏 */
|
||||
export declare const MESSAGE_STATE_TTL_MS: number;
|
||||
/** messageStates Map 清理间隔(毫秒) */
|
||||
export declare const MESSAGE_STATE_CLEANUP_INTERVAL_MS = 60000;
|
||||
/** messageStates Map 最大条目数 */
|
||||
export declare const MESSAGE_STATE_MAX_SIZE = 500;
|
||||
/** "思考中"流式消息占位内容 */
|
||||
export declare const THINKING_MESSAGE = "<think></think>";
|
||||
/** 仅包含图片时的消息占位符 */
|
||||
export declare const MEDIA_IMAGE_PLACEHOLDER = "<media:image>";
|
||||
/** 仅包含文件时的消息占位符 */
|
||||
export declare const MEDIA_DOCUMENT_PLACEHOLDER = "<media:document>";
|
||||
/** 获取 MCP 配置的 WebSocket 命令 */
|
||||
export declare const MCP_GET_CONFIG_CMD = "aibot_get_mcp_config";
|
||||
/** MCP 配置拉取超时时间(毫秒) */
|
||||
export declare const MCP_CONFIG_FETCH_TIMEOUT_MS = 15000;
|
||||
export declare const PLUGIN_JSON_FILENAME = "openclaw.plugin.json";
|
||||
export declare const PLUGIN_JSON_PATH: string;
|
||||
/** 默认媒体大小上限(MB) */
|
||||
export declare const DEFAULT_MEDIA_MAX_MB = 5;
|
||||
/** 文本分块大小上限 */
|
||||
export declare const TEXT_CHUNK_LIMIT = 4000;
|
||||
/** 图片大小上限(字节):10MB */
|
||||
export declare const IMAGE_MAX_BYTES: number;
|
||||
/** 视频大小上限(字节):10MB */
|
||||
export declare const VIDEO_MAX_BYTES: number;
|
||||
/** 语音大小上限(字节):2MB */
|
||||
export declare const VOICE_MAX_BYTES: number;
|
||||
/** 文件大小上限(字节):20MB */
|
||||
export declare const FILE_MAX_BYTES: number;
|
||||
/** 文件绝对上限(字节):超过此值无法发送,等于 FILE_MAX_BYTES */
|
||||
export declare const ABSOLUTE_MAX_BYTES: number;
|
||||
/** 上传分片大小(字节,Base64 编码前):512KB */
|
||||
export declare const UPLOAD_CHUNK_SIZE: number;
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 企业微信 DM(私聊)访问控制模块
|
||||
*
|
||||
* 负责私聊策略检查、配对流程
|
||||
*/
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import type { WSClient, WsFrame } from "@wecom/aibot-node-sdk";
|
||||
import type { ResolvedWeComAccount } from "./utils.js";
|
||||
/**
|
||||
* DM Policy 检查结果
|
||||
*/
|
||||
export interface DmPolicyCheckResult {
|
||||
/** 是否允许继续处理消息 */
|
||||
allowed: boolean;
|
||||
/** 是否已发送配对消息(仅在 pairing 模式下) */
|
||||
pairingSent?: boolean;
|
||||
}
|
||||
/**
|
||||
* 检查 DM Policy 访问控制
|
||||
* @returns 检查结果,包含是否允许继续处理
|
||||
*/
|
||||
export declare function checkDmPolicy(params: {
|
||||
senderId: string;
|
||||
isGroup: boolean;
|
||||
account: ResolvedWeComAccount;
|
||||
wsClient: WSClient;
|
||||
frame: WsFrame;
|
||||
runtime: RuntimeEnv;
|
||||
}): Promise<DmPolicyCheckResult>;
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 企业微信群组访问控制模块
|
||||
*
|
||||
* 负责群组策略检查(groupPolicy、群组白名单、群内发送者白名单)
|
||||
*/
|
||||
import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import type { ResolvedWeComAccount } from "./utils.js";
|
||||
/**
|
||||
* 群组策略检查结果
|
||||
*/
|
||||
export interface GroupPolicyCheckResult {
|
||||
/** 是否允许继续处理消息 */
|
||||
allowed: boolean;
|
||||
}
|
||||
/**
|
||||
* 检查群组策略访问控制
|
||||
* @returns 检查结果,包含是否允许继续处理
|
||||
*/
|
||||
export declare function checkGroupPolicy(params: {
|
||||
chatId: string;
|
||||
senderId: string;
|
||||
account: ResolvedWeComAccount;
|
||||
config: OpenClawConfig;
|
||||
runtime: RuntimeEnv;
|
||||
}): GroupPolicyCheckResult;
|
||||
/**
|
||||
* 检查发送者是否在允许列表中(通用)
|
||||
*/
|
||||
export declare function isSenderAllowed(senderId: string, allowFrom: string[]): boolean;
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* 企业微信渠道类型定义
|
||||
*/
|
||||
import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import type { ResolvedWeComAccount } from "./utils.js";
|
||||
import { WeComCommand } from "./const.js";
|
||||
/**
|
||||
* Monitor 配置选项
|
||||
*/
|
||||
export type WeComMonitorOptions = {
|
||||
account: ResolvedWeComAccount;
|
||||
config: OpenClawConfig;
|
||||
runtime: RuntimeEnv;
|
||||
abortSignal?: AbortSignal;
|
||||
};
|
||||
/**
|
||||
* 消息状态
|
||||
*/
|
||||
export interface MessageState {
|
||||
accumulatedText: string;
|
||||
/** 流式回复的 streamId,用于保持同一个流式回复使用相同的 streamId */
|
||||
streamId?: string;
|
||||
/** 是否有用户可见的文本内容(不包括 <think>...</think> 标签) */
|
||||
hasText?: boolean;
|
||||
/** 是否已成功发送过媒体文件 */
|
||||
hasMedia?: boolean;
|
||||
/** 是否有媒体发送失败(权限不足、文件过大等) */
|
||||
hasMediaFailed?: boolean;
|
||||
/** 媒体发送失败时的纯文本错误摘要(用于替换 thinking 流展示给用户) */
|
||||
mediaErrorSummary?: string;
|
||||
/** deliver 回调是否被调用过(用于区分"核心无回复"和"核心回复了空内容") */
|
||||
deliverCalled?: boolean;
|
||||
}
|
||||
/**
|
||||
* MCP 配置响应体
|
||||
*/
|
||||
export interface McpConfigBody {
|
||||
/** MCP Server 的 StreamableHttp URL */
|
||||
url: string;
|
||||
/** 连接类型,如 "streamable-http" */
|
||||
type?: string;
|
||||
/** 是否已授权 */
|
||||
is_authed?: boolean;
|
||||
/** mcp业务类型 */
|
||||
biz_type?: string;
|
||||
}
|
||||
/**
|
||||
* WebSocket 请求消息基础格式
|
||||
*/
|
||||
export interface WeComRequest {
|
||||
cmd: string;
|
||||
headers: {
|
||||
req_id: string;
|
||||
};
|
||||
body: any;
|
||||
}
|
||||
/**
|
||||
* WebSocket 响应消息格式
|
||||
*/
|
||||
export interface WeComResponse {
|
||||
headers: {
|
||||
req_id: string;
|
||||
};
|
||||
errcode: number;
|
||||
errmsg: string;
|
||||
}
|
||||
/**
|
||||
* 企业微信认证请求
|
||||
*/
|
||||
export interface WeComSubscribeRequest extends WeComRequest {
|
||||
cmd: WeComCommand.SUBSCRIBE;
|
||||
body: {
|
||||
secret: string;
|
||||
bot_id: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 企业微信推送消息格式
|
||||
*/
|
||||
export interface WeComCallbackMessage {
|
||||
cmd: WeComCommand.AIBOT_CALLBACK;
|
||||
headers: {
|
||||
req_id: string;
|
||||
};
|
||||
body: {
|
||||
msgid: string;
|
||||
aibotid: string;
|
||||
chatid: string;
|
||||
chattype: "single" | "group";
|
||||
from: {
|
||||
userid: string;
|
||||
};
|
||||
response_url: string;
|
||||
msgtype: "text" | "image" | "voice" | "video" | "file" | "stream" | "mixed";
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
image?: {
|
||||
/** 图片 URL(通过 URL 方式接收图片时) */
|
||||
url?: string;
|
||||
/** 图片 base64 数据(直接传输时) */
|
||||
base64?: string;
|
||||
md5?: string;
|
||||
};
|
||||
/** 图文混排消息 */
|
||||
mixed?: {
|
||||
msg_item: Array<{
|
||||
msgtype: "text" | "image";
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
image?: {
|
||||
url?: string;
|
||||
base64?: string;
|
||||
md5?: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
quote?: {
|
||||
msgtype: string;
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
image?: {
|
||||
url?: string;
|
||||
aeskey?: string;
|
||||
};
|
||||
file?: {
|
||||
url?: string;
|
||||
aeskey?: string;
|
||||
};
|
||||
};
|
||||
stream?: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 企业微信响应消息格式
|
||||
*/
|
||||
export interface WeComResponseMessage extends WeComRequest {
|
||||
cmd: WeComCommand.AIBOT_RESPONSE;
|
||||
body: {
|
||||
msgtype: "stream" | "text" | "markdown";
|
||||
stream?: {
|
||||
id: string;
|
||||
finish: boolean;
|
||||
content: string;
|
||||
msg_item?: Array<{
|
||||
msgtype: "image" | "file";
|
||||
image?: {
|
||||
base64: string;
|
||||
md5: string;
|
||||
};
|
||||
}>;
|
||||
feedback?: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
markdown?: {
|
||||
content: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* MCP 配置拉取与持久化模块
|
||||
*
|
||||
* 负责:
|
||||
* - 通过 WSClient 发送 aibot_get_mcp_config 请求
|
||||
* - 解析服务端响应,提取 MCP 配置(url、type、is_authed)
|
||||
* - 将配置写入 openclaw.plugin.json 的 wecomMcp 字段
|
||||
*/
|
||||
import { type WSClient } from "@wecom/aibot-node-sdk";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import type { McpConfigBody } from "./interface.js";
|
||||
/**
|
||||
* 通过 WSClient 发送 aibot_get_mcp_config 命令,获取 MCP 配置
|
||||
*
|
||||
* @param wsClient - 已认证的 WSClient 实例
|
||||
* @returns MCP 配置(url、type、is_authed)
|
||||
* @throws 响应错误码非 0 或缺少 url 字段时抛出错误
|
||||
*/
|
||||
export declare function fetchMcpConfig(wsClient: WSClient): Promise<McpConfigBody>;
|
||||
/**
|
||||
* 拉取 MCP 配置并持久化到 openclaw.plugin.json
|
||||
*
|
||||
* 认证成功后调用。失败仅记录日志,不影响 WebSocket 消息正常收发。
|
||||
*
|
||||
* @param wsClient - 已认证的 WSClient 实例
|
||||
* @param accountId - 账户 ID(用于日志)
|
||||
* @param runtime - 运行时环境(用于日志)
|
||||
*/
|
||||
export declare function fetchAndSaveMcpConfig(wsClient: WSClient, accountId: string, runtime: RuntimeEnv): Promise<void>;
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 企业微信媒体(图片)下载和保存模块
|
||||
*
|
||||
* 负责下载、检测格式、保存图片到本地,包含超时保护
|
||||
*/
|
||||
import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import type { WSClient } from "@wecom/aibot-node-sdk";
|
||||
import type { ResolvedWeComAccount } from "./utils.js";
|
||||
/**
|
||||
* 下载并保存所有图片到本地,每张图片的下载带超时保护
|
||||
*/
|
||||
export declare function downloadAndSaveImages(params: {
|
||||
imageUrls: string[];
|
||||
imageAesKeys?: Map<string, string>;
|
||||
account: ResolvedWeComAccount;
|
||||
config: OpenClawConfig;
|
||||
runtime: RuntimeEnv;
|
||||
wsClient: WSClient;
|
||||
}): Promise<Array<{
|
||||
path: string;
|
||||
contentType?: string;
|
||||
}>>;
|
||||
/**
|
||||
* 下载并保存所有文件到本地,每个文件的下载带超时保护
|
||||
*/
|
||||
export declare function downloadAndSaveFiles(params: {
|
||||
fileUrls: string[];
|
||||
fileAesKeys?: Map<string, string>;
|
||||
account: ResolvedWeComAccount;
|
||||
config: OpenClawConfig;
|
||||
runtime: RuntimeEnv;
|
||||
wsClient: WSClient;
|
||||
}): Promise<Array<{
|
||||
path: string;
|
||||
contentType?: string;
|
||||
}>>;
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* 企业微信出站媒体上传工具模块
|
||||
*
|
||||
* 负责:
|
||||
* - 从 mediaUrl 加载文件 buffer(远程 URL 或本地路径均支持)
|
||||
* - 检测 MIME 类型并映射为企微媒体类型
|
||||
* - 文件大小检查与降级策略
|
||||
*/
|
||||
import type { WeComMediaType, WSClient, WsFrameHeaders } from "@wecom/aibot-node-sdk";
|
||||
/** 媒体文件解析结果 */
|
||||
export interface ResolvedMedia {
|
||||
/** 文件数据 */
|
||||
buffer: Buffer;
|
||||
/** 检测到的 MIME 类型 */
|
||||
contentType: string;
|
||||
/** 文件名(从 URL 提取或默认生成) */
|
||||
fileName: string;
|
||||
}
|
||||
/** 文件大小检查结果 */
|
||||
export interface FileSizeCheckResult {
|
||||
/** 最终确定的企微媒体类型(可能被降级) */
|
||||
finalType: WeComMediaType;
|
||||
/** 是否需要拒绝(超过绝对限制) */
|
||||
shouldReject: boolean;
|
||||
/** 拒绝原因(仅 shouldReject=true 时有值) */
|
||||
rejectReason?: string;
|
||||
/** 是否发生了降级 */
|
||||
downgraded: boolean;
|
||||
/** 降级说明(仅 downgraded=true 时有值) */
|
||||
downgradeNote?: string;
|
||||
}
|
||||
/**
|
||||
* 根据 MIME 类型检测企微媒体类型
|
||||
*
|
||||
* @param mimeType - MIME 类型字符串
|
||||
* @returns 企微媒体类型
|
||||
*/
|
||||
export declare function detectWeComMediaType(mimeType: string): WeComMediaType;
|
||||
/**
|
||||
* 从 mediaUrl 加载媒体文件
|
||||
*
|
||||
* 支持远程 URL(http/https)和本地路径(file:// 或绝对路径),
|
||||
* 利用 openclaw plugin-sdk 的 loadOutboundMediaFromUrl 统一处理。
|
||||
*
|
||||
* @param mediaUrl - 媒体文件的 URL 或本地路径
|
||||
* @param mediaLocalRoots - 允许读取本地文件的安全白名单目录
|
||||
* @returns 解析后的媒体文件信息
|
||||
*/
|
||||
export declare function resolveMediaFile(mediaUrl: string, mediaLocalRoots?: readonly string[]): Promise<ResolvedMedia>;
|
||||
/**
|
||||
* 检查文件大小并执行降级策略
|
||||
*
|
||||
* 降级规则:
|
||||
* - voice 非 AMR 格式 → 降级为 file(企微后台仅支持 AMR)
|
||||
* - image 超过 10MB → 降级为 file
|
||||
* - video 超过 10MB → 降级为 file
|
||||
* - voice 超过 2MB → 降级为 file
|
||||
* - file 超过 20MB → 拒绝发送
|
||||
*
|
||||
* @param fileSize - 文件大小(字节)
|
||||
* @param detectedType - 检测到的企微媒体类型
|
||||
* @param contentType - 文件的 MIME 类型(用于语音格式校验)
|
||||
* @returns 大小检查结果
|
||||
*/
|
||||
export declare function applyFileSizeLimits(fileSize: number, detectedType: WeComMediaType, contentType?: string): FileSizeCheckResult;
|
||||
/** uploadAndSendMedia 的参数 */
|
||||
export interface UploadAndSendMediaOptions {
|
||||
/** WSClient 实例 */
|
||||
wsClient: WSClient;
|
||||
/** 媒体文件的 URL 或本地路径 */
|
||||
mediaUrl: string;
|
||||
/** 目标会话 ID(用于 aibot_send_msg 主动发送) */
|
||||
chatId: string;
|
||||
/** 允许读取本地文件的安全白名单目录 */
|
||||
mediaLocalRoots?: readonly string[];
|
||||
/** 日志函数 */
|
||||
log?: (...args: any[]) => void;
|
||||
/** 错误日志函数 */
|
||||
errorLog?: (...args: any[]) => void;
|
||||
}
|
||||
/** uploadAndSendMedia 的返回结果 */
|
||||
export interface UploadAndSendMediaResult {
|
||||
/** 是否发送成功 */
|
||||
ok: boolean;
|
||||
/** 发送后返回的 messageId */
|
||||
messageId?: string;
|
||||
/** 最终的企微媒体类型 */
|
||||
finalType?: WeComMediaType;
|
||||
/** 是否被拒绝(文件过大) */
|
||||
rejected?: boolean;
|
||||
/** 拒绝原因 */
|
||||
rejectReason?: string;
|
||||
/** 是否发生了降级 */
|
||||
downgraded?: boolean;
|
||||
/** 降级说明 */
|
||||
downgradeNote?: string;
|
||||
/** 错误信息 */
|
||||
error?: string;
|
||||
}
|
||||
/**
|
||||
* 公共媒体上传+发送流程
|
||||
*
|
||||
* 统一处理:resolveMediaFile → detectType → sizeCheck → uploadMedia → sendMediaMessage
|
||||
* 媒体消息统一走 aibot_send_msg 主动发送,避免多文件场景下 reqId 只能用一次的问题。
|
||||
* channel.ts 的 sendMedia 和 monitor.ts 的 deliver 回调都使用此函数。
|
||||
*/
|
||||
export declare function uploadAndSendMedia(options: UploadAndSendMediaOptions): Promise<UploadAndSendMediaResult>;
|
||||
/** uploadAndReplyMedia 的参数 */
|
||||
export interface UploadAndReplyMediaOptions {
|
||||
/** WSClient 实例 */
|
||||
wsClient: WSClient;
|
||||
/** 媒体文件的 URL 或本地路径 */
|
||||
mediaUrl: string;
|
||||
/** 原始 WebSocket 帧(用于 aibot_respond_msg 被动回复,携带 req_id) */
|
||||
frame: WsFrameHeaders;
|
||||
/** 允许读取本地文件的安全白名单目录 */
|
||||
mediaLocalRoots?: readonly string[];
|
||||
/** 日志函数 */
|
||||
log?: (...args: any[]) => void;
|
||||
/** 错误日志函数 */
|
||||
errorLog?: (...args: any[]) => void;
|
||||
}
|
||||
/**
|
||||
* 被动回复媒体上传+发送流程
|
||||
*
|
||||
* 统一处理:resolveMediaFile → detectType → sizeCheck → uploadMedia → replyMedia
|
||||
* 通过 aibot_respond_msg 被动回复通道发送媒体消息,可以覆盖之前的 THINKING_MESSAGE。
|
||||
*
|
||||
* 适用场景:回包只有媒体没有文本时,第一个媒体文件用此方法发送以清理 thinking 状态。
|
||||
*/
|
||||
export declare function uploadAndReplyMedia(options: UploadAndReplyMediaOptions): Promise<UploadAndSendMediaResult>;
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 企业微信消息内容解析模块
|
||||
*
|
||||
* 负责从 WsFrame 中提取文本、图片、引用等内容
|
||||
*/
|
||||
export interface MessageBody {
|
||||
msgid: string;
|
||||
aibotid?: string;
|
||||
chatid?: string;
|
||||
chattype: "single" | "group";
|
||||
from: {
|
||||
userid: string;
|
||||
};
|
||||
response_url?: string;
|
||||
msgtype: string;
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
image?: {
|
||||
url?: string;
|
||||
aeskey?: string;
|
||||
};
|
||||
voice?: {
|
||||
content?: string;
|
||||
};
|
||||
mixed?: {
|
||||
msg_item: Array<{
|
||||
msgtype: "text" | "image";
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
image?: {
|
||||
url?: string;
|
||||
aeskey?: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
file?: {
|
||||
url?: string;
|
||||
aeskey?: string;
|
||||
};
|
||||
quote?: {
|
||||
msgtype: string;
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
voice?: {
|
||||
content: string;
|
||||
};
|
||||
image?: {
|
||||
url?: string;
|
||||
aeskey?: string;
|
||||
};
|
||||
file?: {
|
||||
url?: string;
|
||||
aeskey?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
export interface ParsedMessageContent {
|
||||
textParts: string[];
|
||||
imageUrls: string[];
|
||||
imageAesKeys: Map<string, string>;
|
||||
fileUrls: string[];
|
||||
fileAesKeys: Map<string, string>;
|
||||
quoteContent: string | undefined;
|
||||
}
|
||||
/**
|
||||
* 解析消息内容(支持单条消息、图文混排和引用消息)
|
||||
* @returns 提取的文本数组、图片URL数组和引用消息内容
|
||||
*/
|
||||
export declare function parseMessageContent(body: MessageBody): ParsedMessageContent;
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 企业微信消息发送模块
|
||||
*
|
||||
* 负责通过 WSClient 发送回复消息,包含超时保护
|
||||
*/
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import { type WSClient, type WsFrame } from "@wecom/aibot-node-sdk";
|
||||
/**
|
||||
* 发送企业微信回复消息
|
||||
* 供 monitor 内部和 channel outbound 使用
|
||||
*
|
||||
* @returns messageId (streamId)
|
||||
*/
|
||||
export declare function sendWeComReply(params: {
|
||||
wsClient: WSClient;
|
||||
frame: WsFrame;
|
||||
text?: string;
|
||||
runtime: RuntimeEnv;
|
||||
/** 是否为流式回复的最终消息,默认为 true */
|
||||
finish?: boolean;
|
||||
/** 指定 streamId,用于流式回复时保持相同的 streamId */
|
||||
streamId?: string;
|
||||
}): Promise<string>;
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 企业微信 WebSocket 监控器主模块
|
||||
*
|
||||
* 负责:
|
||||
* - 建立和管理 WebSocket 连接
|
||||
* - 协调消息处理流程(解析→策略检查→下载图片→路由回复)
|
||||
* - 资源生命周期管理
|
||||
*
|
||||
* 子模块:
|
||||
* - message-parser.ts : 消息内容解析
|
||||
* - message-sender.ts : 消息发送(带超时保护)
|
||||
* - media-handler.ts : 图片下载和保存(带超时保护)
|
||||
* - group-policy.ts : 群组访问控制
|
||||
* - dm-policy.ts : 私聊访问控制
|
||||
* - state-manager.ts : 全局状态管理(带 TTL 清理)
|
||||
* - timeout.ts : 超时工具
|
||||
*/
|
||||
import type { WeComMonitorOptions } from "./interface.js";
|
||||
export type { WeComMonitorOptions } from "./interface.js";
|
||||
export { WeComCommand } from "./const.js";
|
||||
export { getWeComWebSocket, setReqIdForChat, getReqIdForChatAsync, getReqIdForChat, deleteReqIdForChat, warmupReqIdStore, flushReqIdStore, } from "./state-manager.js";
|
||||
export { sendWeComReply } from "./message-sender.js";
|
||||
/**
|
||||
* 监听企业微信 WebSocket 连接
|
||||
* 使用 aibot-node-sdk 简化连接管理
|
||||
*/
|
||||
export declare function monitorWeComProvider(options: WeComMonitorOptions): Promise<void>;
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 企业微信 onboarding adapter for CLI setup wizard.
|
||||
*/
|
||||
import { type ChannelOnboardingAdapter } from "openclaw/plugin-sdk";
|
||||
export declare const wecomOnboardingAdapter: ChannelOnboardingAdapter;
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* openclaw plugin-sdk 高版本方法兼容层
|
||||
*
|
||||
* 部分方法(如 loadOutboundMediaFromUrl、detectMime、getDefaultMediaLocalRoots)
|
||||
* 仅在较新版本的 openclaw plugin-sdk 中才导出。
|
||||
*
|
||||
* 本模块在加载时一次性探测 SDK 导出,存在则直接 re-export SDK 版本,
|
||||
* 不存在则导出 fallback 实现。其他模块统一从本文件导入,无需关心底层兼容细节。
|
||||
*/
|
||||
/** 与 openclaw plugin-sdk 中 WebMediaResult 兼容的类型 */
|
||||
export type WebMediaResult = {
|
||||
buffer: Buffer;
|
||||
contentType?: string;
|
||||
kind?: string;
|
||||
fileName?: string;
|
||||
};
|
||||
export type OutboundMediaLoadOptions = {
|
||||
maxBytes?: number;
|
||||
mediaLocalRoots?: readonly string[];
|
||||
};
|
||||
export type DetectMimeOptions = {
|
||||
buffer?: Buffer;
|
||||
headerMime?: string | null;
|
||||
filePath?: string;
|
||||
};
|
||||
/**
|
||||
* 检测 MIME 类型(兼容入口)
|
||||
*
|
||||
* 支持两种调用签名以兼容不同使用场景:
|
||||
* - detectMime(buffer) → 旧式调用
|
||||
* - detectMime({ buffer, headerMime, filePath }) → 完整参数
|
||||
*
|
||||
* 优先使用 SDK 版本,不可用时使用 fallback。
|
||||
*/
|
||||
export declare function detectMime(bufferOrOpts: Buffer | DetectMimeOptions): Promise<string | undefined>;
|
||||
/**
|
||||
* 从 URL 或本地路径加载媒体文件(兼容入口)
|
||||
*
|
||||
* 优先使用 SDK 版本,不可用时使用 fallback。
|
||||
* SDK 版本抛出的业务异常(如 LocalMediaAccessError)会直接透传。
|
||||
*/
|
||||
export declare function loadOutboundMediaFromUrl(mediaUrl: string, options?: OutboundMediaLoadOptions): Promise<WebMediaResult>;
|
||||
/**
|
||||
* 获取默认媒体本地路径白名单(兼容入口)
|
||||
*
|
||||
* 优先使用 SDK 版本,不可用时手动构建白名单(与 weclaw/src/media/local-roots.ts 逻辑一致)。
|
||||
*/
|
||||
export declare function getDefaultMediaLocalRoots(): Promise<readonly string[]>;
|
||||
@@ -0,0 +1,31 @@
|
||||
/** Store 配置 */
|
||||
interface ReqIdStoreOptions {
|
||||
/** TTL 毫秒数,超时的 reqId 视为过期(默认 24 小时) */
|
||||
ttlMs?: number;
|
||||
/** 内存最大条目数(默认 200) */
|
||||
memoryMaxSize?: number;
|
||||
/** 磁盘最大条目数(默认 500) */
|
||||
fileMaxEntries?: number;
|
||||
/** 磁盘写入防抖时间(毫秒),默认 1000ms */
|
||||
flushDebounceMs?: number;
|
||||
}
|
||||
export interface PersistentReqIdStore {
|
||||
/** 设置 chatId 对应的 reqId(写入内存 + 防抖写磁盘) */
|
||||
set(chatId: string, reqId: string): void;
|
||||
/** 获取 chatId 对应的 reqId(异步:优先内存,miss 时查磁盘并回填内存) */
|
||||
get(chatId: string): Promise<string | undefined>;
|
||||
/** 同步获取 chatId 对应的 reqId(仅内存) */
|
||||
getSync(chatId: string): string | undefined;
|
||||
/** 删除 chatId 对应的 reqId */
|
||||
delete(chatId: string): void;
|
||||
/** 启动时从磁盘预热内存,返回加载条目数 */
|
||||
warmup(onError?: (error: unknown) => void): Promise<number>;
|
||||
/** 立即将内存数据刷写到磁盘(用于优雅退出) */
|
||||
flush(): Promise<void>;
|
||||
/** 清空内存缓存 */
|
||||
clearMemory(): void;
|
||||
/** 返回内存中的条目数 */
|
||||
memorySize(): number;
|
||||
}
|
||||
export declare function createPersistentReqIdStore(accountId: string, options?: ReqIdStoreOptions): PersistentReqIdStore;
|
||||
export {};
|
||||
@@ -0,0 +1,3 @@
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
||||
export declare function setWeComRuntime(r: PluginRuntime): void;
|
||||
export declare function getWeComRuntime(): PluginRuntime;
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 企业微信全局状态管理模块
|
||||
*
|
||||
* 负责管理 WSClient 实例、消息状态(带 TTL 清理)、ReqId 存储
|
||||
* 解决全局 Map 的内存泄漏问题
|
||||
*/
|
||||
import type { WSClient } from "@wecom/aibot-node-sdk";
|
||||
import type { MessageState } from "./interface.js";
|
||||
/**
|
||||
* 获取指定账户的 WSClient 实例
|
||||
*/
|
||||
export declare function getWeComWebSocket(accountId: string): WSClient | null;
|
||||
/**
|
||||
* 设置指定账户的 WSClient 实例
|
||||
*/
|
||||
export declare function setWeComWebSocket(accountId: string, client: WSClient): void;
|
||||
/**
|
||||
* 删除指定账户的 WSClient 实例
|
||||
*/
|
||||
export declare function deleteWeComWebSocket(accountId: string): void;
|
||||
/**
|
||||
* 启动消息状态定期清理(自动 TTL 清理 + 容量限制)
|
||||
*/
|
||||
export declare function startMessageStateCleanup(): void;
|
||||
/**
|
||||
* 停止消息状态定期清理
|
||||
*/
|
||||
export declare function stopMessageStateCleanup(): void;
|
||||
/**
|
||||
* 设置消息状态
|
||||
*/
|
||||
export declare function setMessageState(messageId: string, state: MessageState): void;
|
||||
/**
|
||||
* 获取消息状态
|
||||
*/
|
||||
export declare function getMessageState(messageId: string): MessageState | undefined;
|
||||
/**
|
||||
* 删除消息状态
|
||||
*/
|
||||
export declare function deleteMessageState(messageId: string): void;
|
||||
/**
|
||||
* 清空所有消息状态
|
||||
*/
|
||||
export declare function clearAllMessageStates(): void;
|
||||
/**
|
||||
* 设置 chatId 对应的 reqId(写入内存 + 防抖写磁盘)
|
||||
*/
|
||||
export declare function setReqIdForChat(chatId: string, reqId: string, accountId?: string): void;
|
||||
/**
|
||||
* 获取 chatId 对应的 reqId(异步:优先内存,miss 时查磁盘并回填内存)
|
||||
*/
|
||||
export declare function getReqIdForChatAsync(chatId: string, accountId?: string): Promise<string | undefined>;
|
||||
/**
|
||||
* 获取 chatId 对应的 reqId(同步:仅内存,保留向后兼容)
|
||||
*/
|
||||
export declare function getReqIdForChat(chatId: string, accountId?: string): string | undefined;
|
||||
/**
|
||||
* 删除 chatId 对应的 reqId
|
||||
*/
|
||||
export declare function deleteReqIdForChat(chatId: string, accountId?: string): void;
|
||||
/**
|
||||
* 启动时预热 reqId 缓存(从磁盘加载到内存)
|
||||
*/
|
||||
export declare function warmupReqIdStore(accountId?: string, log?: (...args: unknown[]) => void): Promise<number>;
|
||||
/**
|
||||
* 立即将 reqId 数据刷写到磁盘(用于优雅退出)
|
||||
*/
|
||||
export declare function flushReqIdStore(accountId?: string): Promise<void>;
|
||||
/**
|
||||
* 清理指定账户的所有资源
|
||||
*/
|
||||
export declare function cleanupAccount(accountId: string): Promise<void>;
|
||||
/**
|
||||
* 清理所有资源(用于进程退出)
|
||||
*/
|
||||
export declare function cleanupAll(): Promise<void>;
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 超时控制工具模块
|
||||
*
|
||||
* 为异步操作提供统一的超时保护机制
|
||||
*/
|
||||
/**
|
||||
* 为 Promise 添加超时保护
|
||||
*
|
||||
* @param promise - 原始 Promise
|
||||
* @param timeoutMs - 超时时间(毫秒)
|
||||
* @param message - 超时错误消息
|
||||
* @returns 带超时保护的 Promise
|
||||
*/
|
||||
export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message?: string): Promise<T>;
|
||||
/**
|
||||
* 超时错误类型
|
||||
*/
|
||||
export declare class TimeoutError extends Error {
|
||||
constructor(message: string);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 企业微信公共工具函数
|
||||
*/
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
||||
/**
|
||||
* 企业微信群组配置
|
||||
*/
|
||||
export interface WeComGroupConfig {
|
||||
/** 群组内发送者白名单(仅列表中的成员消息会被处理) */
|
||||
allowFrom?: Array<string | number>;
|
||||
}
|
||||
/**
|
||||
* 企业微信配置类型
|
||||
*/
|
||||
export interface WeComConfig {
|
||||
enabled?: boolean;
|
||||
websocketUrl?: string;
|
||||
botId?: string;
|
||||
secret?: string;
|
||||
name?: string;
|
||||
allowFrom?: Array<string | number>;
|
||||
dmPolicy?: "open" | "allowlist" | "pairing" | "disabled";
|
||||
/** 群组访问策略:"open" = 允许所有群组(默认),"allowlist" = 仅允许 groupAllowFrom 中的群组,"disabled" = 禁用群组消息 */
|
||||
groupPolicy?: "open" | "allowlist" | "disabled";
|
||||
/** 群组白名单(仅 groupPolicy="allowlist" 时生效) */
|
||||
groupAllowFrom?: Array<string | number>;
|
||||
/** 每个群组的详细配置(如群组内发送者白名单) */
|
||||
groups?: Record<string, WeComGroupConfig>;
|
||||
/** 是否发送"思考中"消息,默认为 true */
|
||||
sendThinkingMessage?: boolean;
|
||||
/** 额外允许访问的本地媒体路径白名单(支持 ~ 表示 home 目录),如 ["~/Downloads", "~/Documents"] */
|
||||
mediaLocalRoots?: string[];
|
||||
}
|
||||
export declare const DefaultWsUrl = "wss://openws.work.weixin.qq.com";
|
||||
export interface ResolvedWeComAccount {
|
||||
accountId: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
websocketUrl: string;
|
||||
botId: string;
|
||||
secret: string;
|
||||
/** 是否发送"思考中"消息,默认为 true */
|
||||
sendThinkingMessage: boolean;
|
||||
config: WeComConfig;
|
||||
}
|
||||
/**
|
||||
* 解析企业微信账户配置
|
||||
*/
|
||||
export declare function resolveWeComAccount(cfg: OpenClawConfig): ResolvedWeComAccount;
|
||||
/**
|
||||
* 设置企业微信账户配置
|
||||
*/
|
||||
export declare function setWeComAccount(cfg: OpenClawConfig, account: Partial<WeComConfig>): OpenClawConfig;
|
||||
Reference in New Issue
Block a user