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 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 instances for each direct message user and group chat. Each instance has its own workspace, configurations, and conversation history, ensuring data isolation and security.
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
- "dynamicAgent": {
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 管理**: 自动为每位私聊用户和每个群聊创建独立的 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
- "dynamicAgent": {
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: "wxwork",
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: true, properties: {} },
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.2.5",
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": "https://github.com/sunnoy/openclaw-plugin-wecom",
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
- // 自动清理过期的流 (10分钟未更新)
13
- setInterval(() => this.cleanup(), 60 * 1000);
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
package/types.js DELETED
@@ -1,5 +0,0 @@
1
- /**
2
- * Enterprise WeChat (WxWork) Type Definitions
3
- */
4
- export {};
5
- //# sourceMappingURL=types.js.map