openclaw-plugin-wecom 0.2.5 → 0.3.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 +55 -4
- package/README_ZH.md +55 -4
- package/client.js +0 -12
- package/dynamic-agent.js +0 -33
- package/index.js +2 -2
- package/openclaw.plugin.json +11 -0
- package/package.json +6 -5
- package/stream-manager.js +20 -2
- package/config.js +0 -11
- package/types.js +0 -5
package/README.md
CHANGED
|
@@ -7,18 +7,65 @@
|
|
|
7
7
|
## ✨ Key Features
|
|
8
8
|
|
|
9
9
|
- 🌊 **Streaming Output**: Based on the latest WeCom AI Bot streaming mechanism for a smooth typing-like response experience.
|
|
10
|
-
- 🤖 **Dynamic Agent Management**: Automatically creates independent Agent
|
|
10
|
+
- 🤖 **Dynamic Agent Management**: Automatically creates an independent Agent per DM user and per group chat. Each Agent has its own workspace and conversation history, keeping data isolated by default.
|
|
11
11
|
- 👥 **Deep Group Chat Integration**: Supports group message parsing and precise triggering via @mentions.
|
|
12
12
|
- 🛠️ **Enhanced Commands**: Built-in support for common commands (e.g., `/new` for a new session, `/status` to check status) with command allowlist configuration.
|
|
13
13
|
- 🔒 **Security & Authentication**: Full support for WeCom message encryption/decryption, URL verification, and sender identity validation.
|
|
14
14
|
- ⚡ **High-Performance Async Processing**: Uses an asynchronous message processing architecture to ensure high responsiveness of the WeCom gateway even during long-running AI inference.
|
|
15
15
|
|
|
16
|
+
## 🤖 Dynamic Agent Routing (How it works)
|
|
17
|
+
|
|
18
|
+
OpenClaw decides which Agent to run by parsing `SessionKey`. This plugin uses that mechanism to provide per-user / per-group isolation:
|
|
19
|
+
|
|
20
|
+
1. When a WeCom message arrives, the plugin generates a deterministic `agentId`:
|
|
21
|
+
- DM: `wxwork-dm-<userId>`
|
|
22
|
+
- Group: `wxwork-group-<chatId>`
|
|
23
|
+
2. The plugin routes the message by setting `SessionKey` to:
|
|
24
|
+
- `agent:<agentId>:<peerKind>:<peerId>`
|
|
25
|
+
3. OpenClaw extracts `<agentId>` from `SessionKey` and will automatically create / reuse the Agent workspace (typically under `~/.openclaw/workspace-<agentId>` for non-default agents).
|
|
26
|
+
|
|
27
|
+
### Multi-tenant by design
|
|
28
|
+
|
|
29
|
+
Dynamic agents act like lightweight “tenants”:
|
|
30
|
+
|
|
31
|
+
- **Per-user / per-group isolation**: each DM user or group chat maps to a dedicated Agent with its own workspace and session store keys.
|
|
32
|
+
- **No extra infra**: no database or tenant registry needed — routing is derived deterministically from the inbound identity.
|
|
33
|
+
|
|
34
|
+
### Dynamic agent config (local config keys)
|
|
35
|
+
|
|
36
|
+
All keys live under `channels.wxwork`:
|
|
37
|
+
|
|
38
|
+
- `dynamicAgents.enabled` (boolean, default: `true`): enable/disable per-user/per-group agents.
|
|
39
|
+
- `dm.createAgentOnFirstMessage` (boolean, default: `true`): whether DMs should use dynamic agents.
|
|
40
|
+
- `groupChat.enabled` (boolean, default: `true`): enable group chat handling.
|
|
41
|
+
- `groupChat.createAgentOnFirstMessage` (boolean, default: `true`): whether group chats should use dynamic agents.
|
|
42
|
+
- `groupChat.requireMention` (boolean, default: `true`): require an @mention to respond in groups.
|
|
43
|
+
- `groupChat.mentionPatterns` (string[], default: `["@"]`): patterns treated as “mention”.
|
|
44
|
+
|
|
45
|
+
If you prefer all WeCom messages to use OpenClaw’s **default Agent**, disable dynamic agents:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"channels": {
|
|
50
|
+
"wxwork": {
|
|
51
|
+
"dynamicAgents": { "enabled": false }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
16
57
|
## 🚀 Quick Start
|
|
17
58
|
|
|
18
59
|
### 1. Install Plugin
|
|
19
60
|
|
|
20
61
|
Run the following in your OpenClaw project directory:
|
|
21
62
|
|
|
63
|
+
```bash
|
|
64
|
+
openclaw plugins install openclaw-plugin-wecom
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Alternatively (if you're managing plugins via `package.json` yourself):
|
|
68
|
+
|
|
22
69
|
```bash
|
|
23
70
|
npm install openclaw-plugin-wecom
|
|
24
71
|
```
|
|
@@ -29,6 +76,11 @@ Add the plugin configuration to your OpenClaw configuration file (e.g., `config.
|
|
|
29
76
|
|
|
30
77
|
```json
|
|
31
78
|
{
|
|
79
|
+
"plugins": {
|
|
80
|
+
"entries": {
|
|
81
|
+
"openclaw-plugin-wecom": { "enabled": true }
|
|
82
|
+
}
|
|
83
|
+
},
|
|
32
84
|
"channels": {
|
|
33
85
|
"wxwork": {
|
|
34
86
|
"enabled": true,
|
|
@@ -44,9 +96,8 @@ Add the plugin configuration to your OpenClaw configuration file (e.g., `config.
|
|
|
44
96
|
"enabled": true,
|
|
45
97
|
"allowlist": ["/new", "/status", "/help", "/compact"]
|
|
46
98
|
},
|
|
47
|
-
"
|
|
48
|
-
"enabled": true
|
|
49
|
-
"prefix": "wxwork-"
|
|
99
|
+
"dynamicAgents": {
|
|
100
|
+
"enabled": true
|
|
50
101
|
}
|
|
51
102
|
}
|
|
52
103
|
}
|
package/README_ZH.md
CHANGED
|
@@ -7,18 +7,65 @@
|
|
|
7
7
|
## ✨ 核心特性
|
|
8
8
|
|
|
9
9
|
- 🌊 **流式输出 (Streaming)**: 基于企业微信最新的 AI 机器人流式分片机制,实现流畅的打字机式回复体验。
|
|
10
|
-
- 🤖 **动态 Agent 管理**:
|
|
10
|
+
- 🤖 **动态 Agent 管理**: 默认按“每个私聊用户 / 每个群聊”自动创建独立 Agent。每个 Agent 拥有独立的工作区与对话上下文,实现更强的数据隔离。
|
|
11
11
|
- 👥 **群聊深度集成**: 支持群聊消息解析,可通过 @提及(At-mention)精准触发机器人响应。
|
|
12
12
|
- 🛠️ **指令增强**: 内置常用指令支持(如 `/new` 开启新会话、`/status` 查看状态等),并提供指令白名单配置功能。
|
|
13
13
|
- 🔒 **安全与认证**: 完整支持企业微信消息加解密、URL 验证及发送者身份校验。
|
|
14
14
|
- ⚡ **高性能异步处理**: 采用异步消息处理架构,确保即使在长耗时 AI 推理过程中,企业微信网关也能保持高响应性。
|
|
15
15
|
|
|
16
|
+
## 🤖 动态 Agent 路由(工作原理)
|
|
17
|
+
|
|
18
|
+
OpenClaw 会通过解析 `SessionKey` 来决定本次消息由哪个 Agent 处理。本插件利用这一机制实现“按人/按群隔离”:
|
|
19
|
+
|
|
20
|
+
1. 企业微信消息到达后,插件会生成一个确定性的 `agentId`:
|
|
21
|
+
- 私聊:`wxwork-dm-<userId>`
|
|
22
|
+
- 群聊:`wxwork-group-<chatId>`
|
|
23
|
+
2. 插件将消息路由到该 Agent:把 `SessionKey` 设为
|
|
24
|
+
- `agent:<agentId>:<peerKind>:<peerId>`
|
|
25
|
+
3. OpenClaw 从 `SessionKey` 中提取 `<agentId>`,并自动创建/复用对应的 Agent 工作区(非默认 Agent 通常落在 `~/.openclaw/workspace-<agentId>`)。
|
|
26
|
+
|
|
27
|
+
### 多租户(按人/按群隔离)亮点
|
|
28
|
+
|
|
29
|
+
动态 Agent 可以理解为“轻量多租户”实现:
|
|
30
|
+
|
|
31
|
+
- **按用户/按群聊隔离**:每个私聊用户、每个群聊都映射到独立 Agent,拥有独立 workspace 与会话存储 key。
|
|
32
|
+
- **无需额外基础设施**:不需要租户表/数据库;路由完全由消息身份确定性推导得到。
|
|
33
|
+
|
|
34
|
+
### 动态 Agent 配置(本地配置项)
|
|
35
|
+
|
|
36
|
+
配置都在 `channels.wxwork` 下:
|
|
37
|
+
|
|
38
|
+
- `dynamicAgents.enabled`(boolean,默认:`true`):是否启用按人/按群动态 Agent。
|
|
39
|
+
- `dm.createAgentOnFirstMessage`(boolean,默认:`true`):私聊是否使用动态 Agent。
|
|
40
|
+
- `groupChat.enabled`(boolean,默认:`true`):是否启用群聊处理。
|
|
41
|
+
- `groupChat.createAgentOnFirstMessage`(boolean,默认:`true`):群聊是否使用动态 Agent。
|
|
42
|
+
- `groupChat.requireMention`(boolean,默认:`true`):群聊是否必须 @ 提及才响应。
|
|
43
|
+
- `groupChat.mentionPatterns`(string[],默认:`["@"]`):哪些字符串算作“提及”。
|
|
44
|
+
|
|
45
|
+
如果你希望企业微信消息全部进入 OpenClaw 的**默认 Agent**,可以关闭动态 Agent:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"channels": {
|
|
50
|
+
"wxwork": {
|
|
51
|
+
"dynamicAgents": { "enabled": false }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
16
57
|
## 🚀 快速开始
|
|
17
58
|
|
|
18
59
|
### 1. 安装插件
|
|
19
60
|
|
|
20
61
|
在你的 OpenClaw 项目目录中运行:
|
|
21
62
|
|
|
63
|
+
```bash
|
|
64
|
+
openclaw plugins install openclaw-plugin-wecom
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
或者(如果你习惯自己在 `package.json` 中管理依赖):
|
|
68
|
+
|
|
22
69
|
```bash
|
|
23
70
|
npm install openclaw-plugin-wecom
|
|
24
71
|
```
|
|
@@ -29,6 +76,11 @@ npm install openclaw-plugin-wecom
|
|
|
29
76
|
|
|
30
77
|
```json
|
|
31
78
|
{
|
|
79
|
+
"plugins": {
|
|
80
|
+
"entries": {
|
|
81
|
+
"openclaw-plugin-wecom": { "enabled": true }
|
|
82
|
+
}
|
|
83
|
+
},
|
|
32
84
|
"channels": {
|
|
33
85
|
"wxwork": {
|
|
34
86
|
"enabled": true,
|
|
@@ -44,9 +96,8 @@ npm install openclaw-plugin-wecom
|
|
|
44
96
|
"enabled": true,
|
|
45
97
|
"allowlist": ["/new", "/status", "/help", "/compact"]
|
|
46
98
|
},
|
|
47
|
-
"
|
|
48
|
-
"enabled": true
|
|
49
|
-
"prefix": "wxwork-"
|
|
99
|
+
"dynamicAgents": {
|
|
100
|
+
"enabled": true
|
|
50
101
|
}
|
|
51
102
|
}
|
|
52
103
|
}
|
package/client.js
CHANGED
|
@@ -125,15 +125,3 @@ export async function sendTemplateCardReply(responseUrl, card) {
|
|
|
125
125
|
template_card: card,
|
|
126
126
|
});
|
|
127
127
|
}
|
|
128
|
-
|
|
129
|
-
// 兼容旧代码 - 保留 WxWorkClient 类名但简化实现
|
|
130
|
-
export class WxWorkClient {
|
|
131
|
-
constructor(config) {
|
|
132
|
-
logger.warn("WxWorkClient is deprecated for AI Bot, use sendReplyMessage directly");
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// 保留兼容方法
|
|
136
|
-
async sendStreamResponse(responseUrl, message) {
|
|
137
|
-
return sendReplyMessage(responseUrl, message);
|
|
138
|
-
}
|
|
139
|
-
}
|
package/dynamic-agent.js
CHANGED
|
@@ -118,36 +118,3 @@ export function extractGroupMessageContent(content, config) {
|
|
|
118
118
|
|
|
119
119
|
return cleanContent.trim();
|
|
120
120
|
}
|
|
121
|
-
|
|
122
|
-
// ============ 兼容性导出 ============
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* @deprecated 不再需要,OpenClaw 会自动创建
|
|
126
|
-
*/
|
|
127
|
-
export async function createDynamicAgent({ chatType, peerId, config }) {
|
|
128
|
-
const agentId = generateAgentId(chatType, peerId);
|
|
129
|
-
logger.debug("createDynamicAgent called (no-op, OpenClaw handles creation)", { agentId });
|
|
130
|
-
return { success: true, agentId, error: null };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* @deprecated 不再需要
|
|
135
|
-
*/
|
|
136
|
-
export function ensureDynamicAgent({ chatType, peerId, config }) {
|
|
137
|
-
const agentId = generateAgentId(chatType, peerId);
|
|
138
|
-
return { success: true, agentId, isNew: false };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* @deprecated 不再需要,OpenClaw 自动处理
|
|
143
|
-
*/
|
|
144
|
-
export function agentExists(config, agentId) {
|
|
145
|
-
return true; // 总是返回 true,让 OpenClaw 处理
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* @deprecated 不再需要
|
|
150
|
-
*/
|
|
151
|
-
export function clearAgentCache(agentId) {
|
|
152
|
-
// no-op
|
|
153
|
-
}
|
package/index.js
CHANGED
|
@@ -807,10 +807,10 @@ async function deliverWxWorkReply({ payload, account, responseUrl, senderId, str
|
|
|
807
807
|
// =============================================================================
|
|
808
808
|
|
|
809
809
|
const plugin = {
|
|
810
|
-
id: "
|
|
810
|
+
id: "openclaw-plugin-wecom",
|
|
811
811
|
name: "Enterprise WeChat",
|
|
812
812
|
description: "Enterprise WeChat AI Bot channel plugin for OpenClaw",
|
|
813
|
-
configSchema: { type: "object", additionalProperties:
|
|
813
|
+
configSchema: { type: "object", additionalProperties: false, properties: {} },
|
|
814
814
|
register(api) {
|
|
815
815
|
logger.info("WxWork plugin registering...");
|
|
816
816
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "openclaw-plugin-wecom",
|
|
3
|
+
"name": "OpenClaw WeCom (WxWork)",
|
|
4
|
+
"description": "Enterprise WeChat (WeCom/WxWork) messaging channel plugin for OpenClaw",
|
|
5
|
+
"channels": ["wxwork"],
|
|
6
|
+
"configSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {}
|
|
10
|
+
}
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-plugin-wecom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Enterprise WeChat AI Bot channel plugin for OpenClaw",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
8
8
|
"index.js",
|
|
9
9
|
"client.js",
|
|
10
|
-
"config.js",
|
|
11
10
|
"crypto.js",
|
|
12
11
|
"dynamic-agent.js",
|
|
13
12
|
"logger.js",
|
|
@@ -16,9 +15,9 @@
|
|
|
16
15
|
"LICENSE",
|
|
17
16
|
"CONTRIBUTING.md",
|
|
18
17
|
"stream-manager.js",
|
|
19
|
-
"types.js",
|
|
20
18
|
"utils.js",
|
|
21
|
-
"webhook.js"
|
|
19
|
+
"webhook.js",
|
|
20
|
+
"openclaw.plugin.json"
|
|
22
21
|
],
|
|
23
22
|
"dependencies": {
|
|
24
23
|
"fast-xml-parser": "^4.2.0"
|
|
@@ -31,8 +30,10 @@
|
|
|
31
30
|
"id": "wxwork",
|
|
32
31
|
"label": "Enterprise WeChat",
|
|
33
32
|
"selectionLabel": "Enterprise WeChat (AI Bot)",
|
|
34
|
-
"docsPath": "
|
|
33
|
+
"docsPath": "/channels/wxwork",
|
|
34
|
+
"docsLabel": "wxwork",
|
|
35
35
|
"blurb": "Support for Enterprise WeChat (WeCom) AI Bot integration",
|
|
36
|
+
"order": 90,
|
|
36
37
|
"aliases": [
|
|
37
38
|
"wecom",
|
|
38
39
|
"wework"
|
package/stream-manager.js
CHANGED
|
@@ -8,9 +8,23 @@ class StreamManager {
|
|
|
8
8
|
constructor() {
|
|
9
9
|
// streamId -> { content: string, finished: boolean, updatedAt: number, feedbackId: string|null, msgItem: Array }
|
|
10
10
|
this.streams = new Map();
|
|
11
|
+
this._cleanupInterval = null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 启动定时清理(避免在 import 时产生常驻 side-effect)
|
|
16
|
+
*/
|
|
17
|
+
startCleanup() {
|
|
18
|
+
if (this._cleanupInterval) return;
|
|
19
|
+
this._cleanupInterval = setInterval(() => this.cleanup(), 60 * 1000);
|
|
20
|
+
// 不阻止进程退出(例如 npm pack / import smoke test)
|
|
21
|
+
this._cleanupInterval.unref?.();
|
|
22
|
+
}
|
|
11
23
|
|
|
12
|
-
|
|
13
|
-
|
|
24
|
+
stopCleanup() {
|
|
25
|
+
if (!this._cleanupInterval) return;
|
|
26
|
+
clearInterval(this._cleanupInterval);
|
|
27
|
+
this._cleanupInterval = null;
|
|
14
28
|
}
|
|
15
29
|
|
|
16
30
|
/**
|
|
@@ -20,6 +34,7 @@ class StreamManager {
|
|
|
20
34
|
* @param {string} options.feedbackId - 反馈追踪ID (最长256字节)
|
|
21
35
|
*/
|
|
22
36
|
createStream(streamId, options = {}) {
|
|
37
|
+
this.startCleanup();
|
|
23
38
|
logger.debug("Creating stream", { streamId, feedbackId: options.feedbackId });
|
|
24
39
|
this.streams.set(streamId, {
|
|
25
40
|
content: "",
|
|
@@ -40,6 +55,7 @@ class StreamManager {
|
|
|
40
55
|
* @param {Array} options.msgItem - 图文混排列表 (仅finish=true时有效)
|
|
41
56
|
*/
|
|
42
57
|
updateStream(streamId, content, finished = false, options = {}) {
|
|
58
|
+
this.startCleanup();
|
|
43
59
|
const stream = this.streams.get(streamId);
|
|
44
60
|
if (!stream) {
|
|
45
61
|
logger.warn("Stream not found for update", { streamId });
|
|
@@ -81,6 +97,7 @@ class StreamManager {
|
|
|
81
97
|
* 追加内容到流 (用于流式生成)
|
|
82
98
|
*/
|
|
83
99
|
appendStream(streamId, chunk) {
|
|
100
|
+
this.startCleanup();
|
|
84
101
|
const stream = this.streams.get(streamId);
|
|
85
102
|
if (!stream) {
|
|
86
103
|
logger.warn("Stream not found for append", { streamId });
|
|
@@ -103,6 +120,7 @@ class StreamManager {
|
|
|
103
120
|
* 标记流为完成状态
|
|
104
121
|
*/
|
|
105
122
|
finishStream(streamId) {
|
|
123
|
+
this.startCleanup();
|
|
106
124
|
const stream = this.streams.get(streamId);
|
|
107
125
|
if (!stream) {
|
|
108
126
|
logger.warn("Stream not found for finish", { streamId });
|
package/config.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
export const WxWorkConfigSchema = z.object({
|
|
3
|
-
enabled: z.boolean().default(false),
|
|
4
|
-
corpId: z.string().describe("Enterprise ID (CorpID)"),
|
|
5
|
-
agentId: z.string().describe("Agent ID (Application ID)"),
|
|
6
|
-
secret: z.string().describe("Application Secret"),
|
|
7
|
-
token: z.string().describe("Webhook Token"),
|
|
8
|
-
encodingAesKey: z.string().describe("Webhook EncodingAESKey"),
|
|
9
|
-
webhookPath: z.string().default("/webhooks/wxwork").describe("Webhook URL path"),
|
|
10
|
-
});
|
|
11
|
-
//# sourceMappingURL=config.js.map
|