@huo15/openclaw-enhance 1.0.0 → 1.1.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
@@ -2,15 +2,29 @@
2
2
 
3
3
  > 非侵入式增强你的 OpenClaw Agent — 借鉴 Claude Code 的最佳实践
4
4
 
5
+ ## 多 Agent 隔离 (v1.1.0)
6
+
7
+ 完全适配 WeCom 插件的**动态 Agent** 功能。每个企微用户/群组拥有独立的:
8
+ - 记忆空间(不同用户的记忆互不可见)
9
+ - 安全审计日志(按 Agent 独立记录)
10
+ - 工作流集合(每个用户可定义自己的工作流)
11
+ - 仪表盘视图(支持按 Agent 筛选)
12
+
13
+ **实现原理**:
14
+ - 工具使用 `OpenClawPluginToolFactory` 模式,从 `ctx.agentId` 获取当前 Agent
15
+ - 钩子从 `ctx.agentId` 获取当前 Agent
16
+ - SQLite 所有表包含 `agent_id` 列,查询时自动按 Agent 过滤
17
+ - v1.0 数据自动迁移(补充 `agent_id = 'main'`)
18
+
5
19
  ## 功能模块
6
20
 
7
21
  | 模块 | 说明 | 工具 |
8
22
  |------|------|------|
9
- | **结构化记忆** | 按类型分类存储记忆(user/project/feedback/reference/decision | `enhance_memory_store` `enhance_memory_search` `enhance_memory_review` |
10
- | **工具安全** | 可配置的工具调用拦截规则 + 审计日志 | `enhance_safety_log` `enhance_safety_rules` |
11
- | **提示词增强** | 自动注入任务分类、质量指引、记忆上下文 | 自动(通过 hook) |
12
- | **工作流自动化** | 触发词驱动的行为指令注入 | `enhance_workflow_define` `enhance_workflow_list` `enhance_workflow_delete` |
13
- | **仪表盘** | Web UI 查看记忆/安全/工作流状态 | `http://localhost:18789/enhance/` |
23
+ | **结构化记忆** | 按类型分类存储记忆(user/project/feedback/reference/decision),按 Agent 隔离 | `enhance_memory_store` `enhance_memory_search` `enhance_memory_review` |
24
+ | **工具安全** | 可配置的工具调用拦截规则 + Agent 隔离的审计日志 | `enhance_safety_log` `enhance_safety_rules` |
25
+ | **提示词增强** | 自动注入任务分类、质量指引、当前 Agent 的记忆上下文 | 自动(通过 hook) |
26
+ | **工作流自动化** | 触发词驱动的行为指令注入,按 Agent 隔离 | `enhance_workflow_define` `enhance_workflow_list` `enhance_workflow_delete` |
27
+ | **仪表盘** | Web UI 查看记忆/安全/工作流状态,支持按 Agent 筛选 | `http://localhost:18789/enhance/` |
14
28
 
15
29
  ## 增强技能
16
30
 
@@ -62,10 +76,22 @@ openclaw restart
62
76
  }
63
77
  ```
64
78
 
79
+ ## 与 WeCom 动态 Agent 配合
80
+
81
+ 当 WeCom 插件启用 `dynamicAgents` 后,每个用户/群组会被分配一个独立的 `agentId`(如 `wecom-acct-ws-dm-hidaomax`)。增强包自动:
82
+
83
+ 1. **记忆隔离** — 用户 A 存的记忆,用户 B 看不到
84
+ 2. **日志隔离** — 每个用户的安全事件独立记录
85
+ 3. **工作流隔离** — 用户 A 定义的工作流不会影响用户 B
86
+ 4. **上下文隔离** — 提示词增强只注入当前用户的记忆
87
+
88
+ 仪表盘支持 `?agent=wecom-acct-ws-dm-hidaomax` 参数查看特定用户数据。
89
+
65
90
  ## 设计理念
66
91
 
67
92
  - **非侵入**: 不修改 OpenClaw 核心代码,完全通过插件 API
68
93
  - **模块化**: 每个模块可独立开关
94
+ - **隔离优先**: 天然适配多 Agent 架构
69
95
  - **借鉴但不照搬**: 取 Claude Code 的精华,适配 OpenClaw 的架构
70
96
 
71
97
  ## License
package/index.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  /**
2
2
  * 龙虾增强包 (OpenClaw Enhancement Kit)
3
3
  *
4
- * 非侵入式增强插件:
5
- * - 模块1: 结构化记忆系统(借鉴 Claude Code auto-memory)
6
- * - 模块2: 工具安全守卫(借鉴 Claude Code 权限系统)
7
- * - 模块3: 提示词增强(借鉴 Claude Code systemPromptSections)
8
- * - 模块4: 工作流自动化(借鉴 Claude Code hooks 事件驱动)
9
- * - 模块5: 增强仪表盘
4
+ * 非侵入式增强插件(v1.1.0 — 多 Agent 隔离):
5
+ * - 模块1: 结构化记忆系统(按 agentId 隔离,借鉴 Claude Code auto-memory)
6
+ * - 模块2: 工具安全守卫(按 agentId 记录日志,借鉴 Claude Code 权限系统)
7
+ * - 模块3: 提示词增强(按 agentId 注入上下文,借鉴 Claude Code systemPromptSections)
8
+ * - 模块4: 工作流自动化(按 agentId 隔离工作流,借鉴 Claude Code hooks 事件驱动)
9
+ * - 模块5: 增强仪表盘(支持按 Agent 筛选)
10
+ *
11
+ * 完全适配 WeCom 插件的动态 Agent 功能:
12
+ * - 工具使用 OpenClawPluginToolFactory 模式,从 ctx.agentId 获取当前 Agent
13
+ * - 钩子从 ctx.agentId 获取当前 Agent
14
+ * - 每个企微用户/群组的数据完全隔离
10
15
  */
11
16
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
12
17
  import { registerStructuredMemory } from "./src/modules/structured-memory.js";
@@ -64,6 +69,6 @@ export default definePluginEntry({
64
69
  }
65
70
  }
66
71
 
67
- api.logger.info(`[enhance] 龙虾增强包 v1.0.0 已加载,启用模块: ${loaded.join("、")}`);
72
+ api.logger.info(`[enhance] 龙虾增强包 v1.1.0 已加载(多 Agent 隔离),启用模块: ${loaded.join("、")}`);
68
73
  },
69
74
  });
@@ -2,7 +2,7 @@
2
2
  "id": "enhance",
3
3
  "name": "龙虾增强包 (OpenClaw Enhancement Kit)",
4
4
  "description": "非侵入式增强:结构化记忆、工具安全守卫、提示词增强、工作流自动化、仪表盘",
5
- "version": "1.0.0",
5
+ "version": "1.1.0",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huo15/openclaw-enhance",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "龙虾增强包 — 结构化记忆、工具安全、提示词增强、工作流自动化、仪表盘",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -1,15 +1,23 @@
1
1
  /**
2
- * 模块5: 增强仪表盘
2
+ * 模块5: 增强仪表盘(多 Agent 隔离版)
3
3
  *
4
- * 通过 gateway HTTP 路由提供可视化状态页面
4
+ * 支持按 agentId 筛选数据,默认显示全局聚合视图。
5
+ * URL 参数: ?agent=<agentId> 查看特定 Agent 数据
5
6
  */
6
7
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
7
- import { getDb, getMemoryStats, getSafetyStats, getRecentMemories, getRecentSafetyEvents } from "../utils/sqlite-store.js";
8
+ import {
9
+ getDb,
10
+ getMemoryStats,
11
+ getSafetyStats,
12
+ getRecentMemories,
13
+ getRecentSafetyEvents,
14
+ getAllAgentIds,
15
+ } from "../utils/sqlite-store.js";
8
16
  import { existsSync, readFileSync } from "node:fs";
9
17
  import { join } from "node:path";
10
- import type { DashboardConfig, Workflow } from "../types.js";
18
+ import { DEFAULT_AGENT_ID, type DashboardConfig, type Workflow } from "../types.js";
11
19
 
12
- function loadWorkflows(openclawDir: string): Workflow[] {
20
+ function loadAllWorkflows(openclawDir: string): Workflow[] {
13
21
  const path = join(openclawDir, "memory", "enhance-workflows.json");
14
22
  if (!existsSync(path)) return [];
15
23
  try {
@@ -27,31 +35,44 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
27
35
  <title>龙虾增强包 — 仪表盘</title>
28
36
  <style>
29
37
  *{margin:0;padding:0;box-sizing:border-box}
30
- body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0f1117;color:#e0e0e0;padding:24px;max-width:960px;margin:0 auto}
38
+ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0f1117;color:#e0e0e0;padding:24px;max-width:1060px;margin:0 auto}
31
39
  h1{font-size:1.8em;margin-bottom:8px;color:#ff6b35}
32
- .subtitle{color:#888;margin-bottom:24px;font-size:0.95em}
33
- .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:32px}
40
+ .subtitle{color:#888;margin-bottom:16px;font-size:0.95em}
41
+ .agent-bar{display:flex;align-items:center;gap:12px;margin-bottom:24px;flex-wrap:wrap}
42
+ .agent-bar label{color:#888;font-size:0.9em}
43
+ .agent-bar select{background:#1a1d27;color:#e0e0e0;border:1px solid #2a2d37;border-radius:6px;padding:6px 12px;font-size:0.9em}
44
+ .agent-bar .current{color:#ff6b35;font-size:0.85em;font-weight:600}
45
+ .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin-bottom:32px}
34
46
  .card{background:#1a1d27;border-radius:12px;padding:20px;border:1px solid #2a2d37}
35
- .card h3{font-size:0.85em;color:#888;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px}
47
+ .card h3{font-size:0.8em;color:#888;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px}
36
48
  .card .value{font-size:2em;font-weight:700;color:#ff6b35}
37
- .card .label{font-size:0.8em;color:#666;margin-top:4px}
49
+ .card .label{font-size:0.75em;color:#666;margin-top:4px}
38
50
  .section{margin-bottom:32px}
39
51
  .section h2{font-size:1.2em;margin-bottom:12px;color:#ccc;border-bottom:1px solid #2a2d37;padding-bottom:8px}
40
52
  table{width:100%;border-collapse:collapse}
41
53
  th,td{text-align:left;padding:8px 12px;border-bottom:1px solid #1a1d27}
42
- th{color:#888;font-size:0.8em;text-transform:uppercase;letter-spacing:1px}
43
- td{font-size:0.9em}
54
+ th{color:#888;font-size:0.75em;text-transform:uppercase;letter-spacing:1px}
55
+ td{font-size:0.85em}
44
56
  .badge{display:inline-block;padding:2px 8px;border-radius:4px;font-size:0.75em;font-weight:600}
45
57
  .badge-block{background:#ff4444;color:#fff}
46
58
  .badge-log{background:#444;color:#ccc}
47
59
  .badge-allow{background:#2a5a2a;color:#8f8}
60
+ .agent-tag{background:#2a2d37;color:#ff6b35;padding:1px 6px;border-radius:3px;font-size:0.75em;margin-left:4px}
48
61
  .empty{color:#555;font-style:italic;padding:16px 0}
49
62
  footer{text-align:center;color:#444;font-size:0.8em;margin-top:40px}
50
63
  </style>
51
64
  </head>
52
65
  <body>
53
66
  <h1>🦞 龙虾增强包</h1>
54
- <p class="subtitle">OpenClaw Enhancement Kit Dashboard</p>
67
+ <p class="subtitle">OpenClaw Enhancement Kit — Multi-Agent Dashboard</p>
68
+
69
+ <div class="agent-bar">
70
+ <label>Agent:</label>
71
+ <select id="agentSelect" onchange="switchAgent(this.value)">
72
+ <option value="">全部 (聚合)</option>
73
+ </select>
74
+ <span class="current" id="currentAgent"></span>
75
+ </div>
55
76
 
56
77
  <div class="grid" id="stats"></div>
57
78
 
@@ -70,16 +91,24 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
70
91
  <div id="workflows"></div>
71
92
  </div>
72
93
 
73
- <footer>龙虾增强包 v1.0.0 — 非侵入式增强</footer>
94
+ <footer>龙虾增强包 v1.1.0 — 多 Agent 隔离</footer>
74
95
 
75
96
  <script>
97
+ let currentAgent='';
98
+ function switchAgent(v){currentAgent=v;const u=new URL(location.href);if(v)u.searchParams.set('agent',v);else u.searchParams.delete('agent');history.replaceState(null,'',u);load();}
76
99
  async function load(){
77
100
  try{
78
- const r=await fetch('/enhance/api/status');
79
- const d=await r.json();
101
+ const u='/enhance/api/status'+(currentAgent?'?agent='+encodeURIComponent(currentAgent):'');
102
+ const r=await fetch(u);const d=await r.json();
103
+ // Populate agent selector
104
+ const sel=document.getElementById('agentSelect');
105
+ const prev=sel.value;
106
+ sel.innerHTML='<option value="">全部 (聚合)</option>'+d.agents.map(a=>'<option value="'+esc(a)+'"'+(a===currentAgent?' selected':'')+'>'+esc(a)+'</option>').join('');
107
+ document.getElementById('currentAgent').textContent=currentAgent?'当前: '+currentAgent:'全部 Agent 聚合视图';
80
108
  // Stats cards
81
109
  const s=document.getElementById('stats');
82
110
  s.innerHTML=[
111
+ card('Agent 数',d.agents.length,'个'),
83
112
  card('记忆总数',d.memory.total,'条'),
84
113
  card('用户记忆',d.memory.user||0,'条'),
85
114
  card('项目记忆',d.memory.project||0,'条'),
@@ -90,25 +119,27 @@ async function load(){
90
119
  // Memories table
91
120
  const m=document.getElementById('memories');
92
121
  if(d.recentMemories.length===0){m.innerHTML='<p class="empty">暂无记忆</p>';} else {
93
- m.innerHTML='<table><tr><th>ID</th><th>类型</th><th>内容</th><th>时间</th></tr>'+
94
- d.recentMemories.map(e=>'<tr><td>#'+e.id+'</td><td>'+e.category+'</td><td>'+esc(e.content).slice(0,60)+'</td><td>'+e.created_at+'</td></tr>').join('')+'</table>';
122
+ m.innerHTML='<table><tr><th>ID</th><th>Agent</th><th>类型</th><th>���容</th><th>时间</th></tr>'+
123
+ d.recentMemories.map(e=>'<tr><td>#'+e.id+'</td><td><span class="agent-tag">'+esc(e.agent_id)+'</span></td><td>'+e.category+'</td><td>'+esc(e.content).slice(0,50)+'</td><td>'+e.created_at+'</td></tr>').join('')+'</table>';
95
124
  }
96
125
  // Safety table
97
126
  const sf=document.getElementById('safety');
98
127
  if(d.recentSafety.length===0){sf.innerHTML='<p class="empty">暂无安全事件</p>';} else {
99
- sf.innerHTML='<table><tr><th>动作</th><th>工具</th><th>参数</th><th>时间</th></tr>'+
100
- d.recentSafety.map(e=>'<tr><td><span class="badge badge-'+e.action+'">'+e.action+'</span></td><td>'+e.tool+'</td><td>'+esc(e.params||'').slice(0,40)+'</td><td>'+e.created_at+'</td></tr>').join('')+'</table>';
128
+ sf.innerHTML='<table><tr><th>动作</th><th>Agent</th><th>工具</th><th>参数</th><th>时间</th></tr>'+
129
+ d.recentSafety.map(e=>'<tr><td><span class="badge badge-'+e.action+'">'+e.action+'</span></td><td><span class="agent-tag">'+esc(e.agent_id)+'</span></td><td>'+e.tool+'</td><td>'+esc(e.params||'').slice(0,35)+'</td><td>'+e.created_at+'</td></tr>').join('')+'</table>';
101
130
  }
102
131
  // Workflows
103
132
  const w=document.getElementById('workflows');
104
133
  if(d.workflows.length===0){w.innerHTML='<p class="empty">暂无工作流</p>';} else {
105
- w.innerHTML='<table><tr><th>名称</th><th>触发词</th><th>状态</th></tr>'+
106
- d.workflows.map(e=>'<tr><td>'+esc(e.name)+'</td><td>'+esc(e.trigger)+'</td><td>'+(e.enabled?'✅':'⏸️')+'</td></tr>').join('')+'</table>';
134
+ w.innerHTML='<table><tr><th>名称</th><th>Agent</th><th>触发词</th><th>状态</th></tr>'+
135
+ d.workflows.map(e=>'<tr><td>'+esc(e.name)+'</td><td><span class="agent-tag">'+esc(e.agent_id||'main')+'</span></td><td>'+esc(e.trigger)+'</td><td>'+(e.enabled?'✅':'⏸️')+'</td></tr>').join('')+'</table>';
107
136
  }
108
137
  }catch(e){document.body.innerHTML+='<p style="color:red">加载失败: '+e.message+'</p>';}
109
138
  }
110
139
  function card(t,v,l){return '<div class="card"><h3>'+t+'</h3><div class="value">'+v+'</div><div class="label">'+l+'</div></div>';}
111
- function esc(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
140
+ function esc(s){return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
141
+ // Init from URL
142
+ const p=new URLSearchParams(location.search);currentAgent=p.get('agent')||'';
112
143
  load();
113
144
  </script>
114
145
  </body>
@@ -117,7 +148,6 @@ load();
117
148
  export function registerDashboard(api: OpenClawPluginApi, _config?: DashboardConfig) {
118
149
  const openclawDir = api.runtime.paths?.home ?? process.env.HOME + "/.openclaw";
119
150
 
120
- // ── HTML 页面 ──
121
151
  api.registerHttpRoute({
122
152
  path: "/enhance",
123
153
  match: "prefix",
@@ -126,17 +156,36 @@ export function registerDashboard(api: OpenClawPluginApi, _config?: DashboardCon
126
156
  const url = new URL(req.url, "http://localhost");
127
157
  const pathname = url.pathname;
128
158
 
129
- // API: 状态数据
130
159
  if (pathname === "/enhance/api/status") {
131
160
  const db = getDb(openclawDir);
132
- const memoryStats = getMemoryStats(db);
133
- const safetyStats = getSafetyStats(db);
134
- const recentMemories = getRecentMemories(db, 10);
135
- const recentSafety = getRecentSafetyEvents(db, 10);
136
- const workflows = loadWorkflows(openclawDir);
161
+ const agentFilter = url.searchParams.get("agent") || undefined;
162
+
163
+ const agents = getAllAgentIds(db);
164
+ const memoryStats = getMemoryStats(db, agentFilter);
165
+ const safetyStats = getSafetyStats(db, agentFilter);
166
+
167
+ // 记忆和安全事件:如果指定了 agent 则按 agent 过滤
168
+ const recentMemories = agentFilter
169
+ ? getRecentMemories(db, agentFilter, 15)
170
+ : (() => {
171
+ // 聚合模式:取各 agent 最近的记忆
172
+ const all: any[] = [];
173
+ for (const aid of agents) {
174
+ all.push(...getRecentMemories(db, aid, 5));
175
+ }
176
+ return all.sort((a, b) => b.created_at.localeCompare(a.created_at)).slice(0, 15);
177
+ })();
178
+
179
+ const recentSafety = getRecentSafetyEvents(db, agentFilter, 15);
180
+
181
+ const allWorkflows = loadAllWorkflows(openclawDir);
182
+ const workflows = agentFilter
183
+ ? allWorkflows.filter((w) => w.agent_id === agentFilter)
184
+ : allWorkflows;
137
185
 
138
186
  return new Response(
139
187
  JSON.stringify({
188
+ agents,
140
189
  memory: memoryStats,
141
190
  safety: safetyStats,
142
191
  recentMemories,
@@ -147,12 +196,11 @@ export function registerDashboard(api: OpenClawPluginApi, _config?: DashboardCon
147
196
  );
148
197
  }
149
198
 
150
- // 默认: 仪表盘 HTML
151
199
  return new Response(DASHBOARD_HTML, {
152
200
  headers: { "Content-Type": "text/html; charset=utf-8" },
153
201
  });
154
202
  },
155
203
  });
156
204
 
157
- api.logger.info("[enhance] 仪表盘模块已加载,访问 /enhance/ 查看");
205
+ api.logger.info("[enhance] 仪表盘模块已加载(多 Agent 视图),访问 /enhance/");
158
206
  }
@@ -1,14 +1,10 @@
1
1
  /**
2
- * 模块3: 提示词增强
2
+ * 模块3: 提示词增强(多 Agent 隔离版)
3
3
  *
4
- * 灵感来自 Claude Code 的模块化系统提示词构建:
5
- * - systemPromptSections 模式
6
- * - 任务分类
7
- * - 响应质量指引
8
- * - 记忆上下文注入
4
+ * 钩子从 ctx.agentId 获取当前 Agent,注入该 Agent 专属的记忆上下文。
9
5
  */
10
6
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
11
- import type { PromptConfig, PromptSection } from "../types.js";
7
+ import { DEFAULT_AGENT_ID, type PromptConfig, type PromptSection } from "../types.js";
12
8
 
13
9
  const SECTIONS: Record<PromptSection, string> = {
14
10
  taskClassification: [
@@ -28,7 +24,7 @@ const SECTIONS: Record<PromptSection, string> = {
28
24
  "- 不要添加请求之外的功能、注释或类型标注",
29
25
  "- 不要为不可能发生的场景做错误处理",
30
26
  "- 三行相似代码好过一个过早的抽象",
31
- "- 失败时先诊断原因,不要盲目重试",
27
+ "- 失败时先诊断���因,不要盲目重试",
32
28
  "- 做破坏性操作前先确认(删除、force push、覆盖未保存的修改)",
33
29
  ].join("\n"),
34
30
 
@@ -46,7 +42,8 @@ const SECTIONS: Record<PromptSection, string> = {
46
42
  export function registerPromptEnhancer(api: OpenClawPluginApi, config?: PromptConfig) {
47
43
  const enabledSections: PromptSection[] = config?.sections ?? ["qualityGuidelines", "memoryContext"];
48
44
 
49
- api.on("before_prompt_build", () => {
45
+ api.on("before_prompt_build", (_event, ctx) => {
46
+ const agentId = ctx?.agentId?.trim() || DEFAULT_AGENT_ID;
50
47
  const parts: string[] = [];
51
48
 
52
49
  for (const section of enabledSections) {
@@ -57,7 +54,10 @@ export function registerPromptEnhancer(api: OpenClawPluginApi, config?: PromptCo
57
54
  if (parts.length === 0) return {};
58
55
 
59
56
  return {
60
- appendSystemContext: "\n\n" + parts.join("\n\n"),
57
+ appendSystemContext: [
58
+ `\n\n<!-- enhance-prompt agent:${agentId} -->`,
59
+ ...parts,
60
+ ].join("\n\n"),
61
61
  };
62
62
  });
63
63