ai-memory-claw 1.0.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.
@@ -0,0 +1,145 @@
1
+ {
2
+ "id": "ai-memory-claw",
3
+ "name": "AI Memory Claw",
4
+ "description": "AI渐进式记忆系统 - 无感运行版",
5
+ "uiHints": {
6
+ "dataDir": {
7
+ "label": "数据存储目录",
8
+ "placeholder": "~/.ai-memory-claw/data",
9
+ "help": "记忆数据存储的目录路径"
10
+ },
11
+ "autoRecall": {
12
+ "label": "自动召回",
13
+ "help": "对话前自动搜索并注入相关记忆"
14
+ },
15
+ "autoCapture": {
16
+ "label": "自动捕获",
17
+ "help": "对话后自动生成记忆"
18
+ },
19
+ "captureStrategy": {
20
+ "label": "捕获策略",
21
+ "help": "always=每次都存, selective=关键词匹配才存"
22
+ },
23
+ "autoRecallInNewSession": {
24
+ "label": "新会话召回",
25
+ "help": "新会话(首次对话)时自动注入1条最相关记忆"
26
+ },
27
+ "newSessionMemoryLimit": {
28
+ "label": "新会话召回数量",
29
+ "help": "新会话时注入的记忆数量(建议1条)"
30
+ },
31
+ "manualTriggerEnabled": {
32
+ "label": "手动触发",
33
+ "help": "用户消息包含关键词时触发召回"
34
+ },
35
+ "manualRecallLimit": {
36
+ "label": "手动召回数量",
37
+ "help": "手动触发时注入的记忆数量"
38
+ },
39
+ "recallThreshold": {
40
+ "label": "召回阈值",
41
+ "help": "相似度阈值,0-1之间,越高越严格"
42
+ },
43
+ "recallLimit": {
44
+ "label": "召回数量",
45
+ "help": "每次召回的记忆数量上限"
46
+ },
47
+ "enableForget": {
48
+ "label": "启用遗忘",
49
+ "help": "自动删除低价值记忆"
50
+ },
51
+ "enableIntegration": {
52
+ "label": "启用整合",
53
+ "help": "自动合并相似记忆"
54
+ }
55
+ },
56
+ "configSchema": {
57
+ "type": "object",
58
+ "properties": {
59
+ "dataDir": {
60
+ "type": "string",
61
+ "default": "~/.ai-memory-claw/data"
62
+ },
63
+ "autoRecall": {
64
+ "type": "boolean",
65
+ "default": true
66
+ },
67
+ "autoCapture": {
68
+ "type": "boolean",
69
+ "default": true
70
+ },
71
+ "captureStrategy": {
72
+ "type": "string",
73
+ "enum": ["always", "selective"],
74
+ "default": "always"
75
+ },
76
+ "autoRecallInNewSession": {
77
+ "type": "boolean",
78
+ "default": true
79
+ },
80
+ "newSessionMemoryLimit": {
81
+ "type": "number",
82
+ "default": 1,
83
+ "minimum": 1,
84
+ "maximum": 5
85
+ },
86
+ "manualTriggerEnabled": {
87
+ "type": "boolean",
88
+ "default": true
89
+ },
90
+ "manualTriggerKeywords": {
91
+ "type": "array",
92
+ "items": { "type": "string" },
93
+ "default": ["记得", "之前", "上次", "以前", "查一下", "看看之前的记忆", "用一下之前的", "参考之前的", "以前是怎么做的", "你还记得吗", "记忆里", "历史上", "之前那次"]
94
+ },
95
+ "manualRecallLimit": {
96
+ "type": "number",
97
+ "default": 3,
98
+ "minimum": 1,
99
+ "maximum": 10
100
+ },
101
+ "recallThreshold": {
102
+ "type": "number",
103
+ "default": 0.6,
104
+ "minimum": 0,
105
+ "maximum": 1
106
+ },
107
+ "recallLimit": {
108
+ "type": "number",
109
+ "default": 2,
110
+ "minimum": 1,
111
+ "maximum": 10
112
+ },
113
+ "captureMaxChars": {
114
+ "type": "number",
115
+ "default": 500,
116
+ "minimum": 100,
117
+ "maximum": 2000
118
+ },
119
+ "enableSummary": {
120
+ "type": "boolean",
121
+ "default": true
122
+ },
123
+ "enableForget": {
124
+ "type": "boolean",
125
+ "default": true
126
+ },
127
+ "enableIntegration": {
128
+ "type": "boolean",
129
+ "default": true
130
+ },
131
+ "forgetIntervalDays": {
132
+ "type": "number",
133
+ "default": 7,
134
+ "minimum": 1,
135
+ "maximum": 30
136
+ },
137
+ "integrationThreshold": {
138
+ "type": "number",
139
+ "default": 0.8,
140
+ "minimum": 0.5,
141
+ "maximum": 1
142
+ }
143
+ }
144
+ }
145
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "ai-memory-claw",
3
+ "version": "1.0.0",
4
+ "description": "AI渐进式记忆系统 OpenClaw 插件 - 无感运行版",
5
+ "main": "index.ts",
6
+ "type": "module",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "vitest",
10
+ "dev": "tsc --watch"
11
+ },
12
+ "keywords": [
13
+ "ai-memory",
14
+ "openclaw",
15
+ "memory-system",
16
+ "ai-memory"
17
+ ],
18
+ "author": "",
19
+ "license": "Apache-2.0",
20
+ "dependencies": {
21
+ "chroma-js": "^2.4.2",
22
+ "uuid": "^9.0.1"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^20.0.0",
26
+ "@types/uuid": "^9.0.0",
27
+ "typescript": "^5.0.0",
28
+ "vitest": "^1.0.0"
29
+ }
30
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * 自动捕获模块
3
+ *
4
+ * 在对话结束后自动生成记忆并存储
5
+ * 强制记忆机制:每次对话 100% 生成记忆
6
+ */
7
+
8
+ import { MemorySystem } from './memory-system';
9
+ import { type MemoryConfig } from './config';
10
+ import { TriggerAnalyzer } from './triggers';
11
+ import { CategoryAnalyzer } from './category';
12
+
13
+ export class AutoCapture {
14
+ private memorySystem: MemorySystem;
15
+ private config: MemoryConfig;
16
+ private triggerAnalyzer: TriggerAnalyzer;
17
+ private categoryAnalyzer: CategoryAnalyzer;
18
+
19
+ constructor(memorySystem: MemorySystem, config: MemoryConfig) {
20
+ this.memorySystem = memorySystem;
21
+ this.config = config;
22
+ this.triggerAnalyzer = new TriggerAnalyzer();
23
+ this.categoryAnalyzer = new CategoryAnalyzer();
24
+ }
25
+
26
+ /**
27
+ * 执行自动捕获
28
+ * 在 agent_end 钩子中调用
29
+ */
30
+ async execute(event: any): Promise<void> {
31
+ try {
32
+ // 1. 检查存储策略
33
+ if (this.config.captureStrategy === 'selective') {
34
+ // 关键词模式:检查是否需要存储
35
+ const hasKeyword = this.checkShouldCapture(event.messages);
36
+ if (!hasKeyword) {
37
+ return;
38
+ }
39
+ }
40
+
41
+ // 2. 生成对话摘要
42
+ const summary = this.generateSummary(event.messages);
43
+ if (!summary) {
44
+ return;
45
+ }
46
+
47
+ // 3. 分析分类
48
+ const { category, subCategory } = this.categoryAnalyzer.analyze(summary.task);
49
+
50
+ // 4. 判断重要性
51
+ const importance = this.triggerAnalyzer.analyze(summary.task);
52
+
53
+ // 5. 提取标签
54
+ const tags = this.triggerAnalyzer.extractTags(summary.task);
55
+
56
+ // 6. 强制存储 (100% 执行)
57
+ await this.memorySystem.store({
58
+ content: summary,
59
+ category,
60
+ subCategory,
61
+ importance,
62
+ tags
63
+ });
64
+
65
+ console.log(`[AutoCapture] 已存储记忆: ${category}/${subCategory} [${importance}]`);
66
+
67
+ } catch (error) {
68
+ console.error('[AutoCapture] 执行失败:', error);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * 检查是否应该捕获 (selective 模式)
74
+ */
75
+ private checkShouldCapture(messages: any[]): boolean {
76
+ const allText = this.extractAllText(messages);
77
+ const importance = this.triggerAnalyzer.analyze(allText);
78
+ // critical/high/medium 都存储,只过滤 low
79
+ return importance !== 'low';
80
+ }
81
+
82
+ /**
83
+ * 提取所有文本
84
+ */
85
+ private extractAllText(messages: any[]): string {
86
+ const texts: string[] = [];
87
+
88
+ for (const msg of messages) {
89
+ if (typeof msg.content === 'string') {
90
+ texts.push(msg.content);
91
+ } else if (Array.isArray(msg.content)) {
92
+ for (const block of msg.content) {
93
+ if (block.type === 'text' && block.text) {
94
+ texts.push(block.text);
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ return texts.join(' ');
101
+ }
102
+
103
+ /**
104
+ * 生成对话摘要
105
+ */
106
+ private generateSummary(messages: any[]): {
107
+ task: string;
108
+ process: string;
109
+ result: string;
110
+ insights: string[];
111
+ } | null {
112
+ // 提取用户消息和 AI 回复
113
+ const userMessages: string[] = [];
114
+ const assistantMessages: string[] = [];
115
+
116
+ for (const msg of messages) {
117
+ const content = this.extractMessageContent(msg);
118
+ if (!content) continue;
119
+
120
+ if (msg.role === 'user') {
121
+ userMessages.push(content);
122
+ } else if (msg.role === 'assistant') {
123
+ assistantMessages.push(content);
124
+ }
125
+ }
126
+
127
+ if (userMessages.length === 0) {
128
+ return null;
129
+ }
130
+
131
+ // 取最后一条用户消息作为任务
132
+ let lastUserMsg = userMessages[userMessages.length - 1];
133
+ let lastAssistantMsg = assistantMessages[assistantMessages.length - 1] || '';
134
+
135
+ // 过滤掉召回的上下文(避免递归嵌套)
136
+ lastUserMsg = this.filterRecallContext(lastUserMsg);
137
+ lastAssistantMsg = this.filterRecallContext(lastAssistantMsg);
138
+
139
+ // 限制长度
140
+ const maxLen = this.config.captureMaxChars;
141
+
142
+ return {
143
+ task: this.truncate(lastUserMsg, maxLen),
144
+ process: this.extractProcess(lastAssistantMsg),
145
+ result: this.truncate(lastAssistantMsg, maxLen),
146
+ insights: this.extractInsights(lastAssistantMsg)
147
+ };
148
+ }
149
+
150
+ /**
151
+ * 提取消息内容
152
+ */
153
+ private extractMessageContent(msg: any): string {
154
+ if (typeof msg.content === 'string') {
155
+ return msg.content;
156
+ }
157
+ if (Array.isArray(msg.content)) {
158
+ for (const block of msg.content) {
159
+ if (block.type === 'text' && block.text) {
160
+ return block.text;
161
+ }
162
+ }
163
+ }
164
+ return '';
165
+ }
166
+
167
+ /**
168
+ * 过滤掉召回的上下文内容
169
+ * 防止递归嵌套
170
+ */
171
+ private filterRecallContext(text: string): string {
172
+ // 移除 <relevant-memories>...</relevant-memories> 标签及内容
173
+ return text.replace(/<relevant-memories>[\s\S]*?<\/relevant-memories>/gi, '').trim();
174
+ }
175
+
176
+ /**
177
+ * 提取处理过程
178
+ */
179
+ private extractProcess(assistantMsg: string): string {
180
+ // 简单提取:取前100字作为处理过程
181
+ return assistantMsg.slice(0, 100);
182
+ }
183
+
184
+ /**
185
+ * 提取洞察
186
+ */
187
+ private extractInsights(assistantMsg: string): string[] {
188
+ const insights: string[] = [];
189
+
190
+ // 简单的洞察提取
191
+ if (/完成|解决|修复/i.test(assistantMsg)) {
192
+ insights.push('任务已完成');
193
+ }
194
+ if (/错误|失败|问题/i.test(assistantMsg)) {
195
+ insights.push('遇到问题');
196
+ }
197
+ if (/建议|可以|推荐/i.test(assistantMsg)) {
198
+ insights.push('提供了建议');
199
+ }
200
+
201
+ return insights;
202
+ }
203
+
204
+ /**
205
+ * 截断文本
206
+ */
207
+ private truncate(text: string, maxLen: number): string {
208
+ if (text.length <= maxLen) return text;
209
+ return text.slice(0, maxLen) + '...';
210
+ }
211
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * 自动召回模块
3
+ *
4
+ * 在 AI 回复前自动搜索相关记忆并注入上下文
5
+ * 触发逻辑:
6
+ * 1. 新会话(第一条消息)自动召回1条
7
+ * 2. 手动触发(关键词)
8
+ */
9
+
10
+ import { MemorySystem } from './memory-system';
11
+ import { type MemoryConfig } from './config';
12
+ import type { Memory } from './types';
13
+
14
+ export class AutoRecall {
15
+ private memorySystem: MemorySystem;
16
+ private config: MemoryConfig;
17
+
18
+ constructor(memorySystem: MemorySystem, config: MemoryConfig) {
19
+ this.memorySystem = memorySystem;
20
+ this.config = config;
21
+ }
22
+
23
+ /**
24
+ * 执行自动召回
25
+ * @param event 钩子事件
26
+ */
27
+ async execute(event: any): Promise<{ prependContext: string } | void> {
28
+ try {
29
+ // 1. 提取用户输入
30
+ const prompt = this.extractUserPrompt(event.prompt);
31
+ if (!prompt || prompt.length < 3) {
32
+ return;
33
+ }
34
+
35
+ // 2. 检查是否为新会话(第一条用户消息)
36
+ const isFirstMessage = this.isFirstUserMessage(event);
37
+
38
+ // 3. 检查是否包含触发关键词
39
+ const hasKeyword = this.hasTriggerKeyword(prompt);
40
+
41
+ let memories: Memory[] = [];
42
+
43
+ if (hasKeyword) {
44
+ // 手动触发模式:有关键词
45
+ console.log('[AutoRecall] 检测到触发关键词,搜索相关记忆...');
46
+ memories = await this.memorySystem.search(prompt, {
47
+ limit: this.config.manualRecallLimit,
48
+ threshold: this.config.recallThreshold
49
+ });
50
+ console.log(`[AutoRecall] 手动触发召回 ${memories.length} 条`);
51
+
52
+ } else if (isFirstMessage && this.config.autoRecallInNewSession) {
53
+ // 新会话自动触发
54
+ console.log('[AutoRecall] 新会话,自动搜索相关记忆...');
55
+ memories = await this.memorySystem.search(prompt, {
56
+ limit: this.config.newSessionMemoryLimit,
57
+ threshold: this.config.recallThreshold
58
+ });
59
+ console.log(`[AutoRecall] 新会话召回 ${memories.length} 条`);
60
+ } else {
61
+ // 非新会话但仍尝试召回(使用较低阈值)
62
+ console.log('[AutoRecall] 尝试召回相关记忆...');
63
+ memories = await this.memorySystem.search(prompt, {
64
+ limit: 1,
65
+ threshold: 0.3 // 较低阈值,扩大召回范围
66
+ });
67
+ console.log(`[AutoRecall] 召回 ${memories.length} 条`);
68
+ }
69
+
70
+ if (memories.length === 0) {
71
+ return;
72
+ }
73
+
74
+ // 4. 格式化并返回
75
+ const context = this.formatMemories(memories);
76
+ return { prependContext: context };
77
+
78
+ } catch (error) {
79
+ console.error('[AutoRecall] 执行失败:', error);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * 判断是否为第一条用户消息(新会话)
85
+ */
86
+ private isFirstUserMessage(event: any): boolean {
87
+ // 从 event.messages 中计算用户消息数量
88
+ const messages = event.messages || [];
89
+
90
+ // 过滤出用户消息
91
+ const userMessages = messages.filter((m: any) => m.role === 'user');
92
+
93
+ // 如果没有用户消息或只有1条,返回 true(新会话)
94
+ return userMessages.length <= 1;
95
+ }
96
+
97
+ /**
98
+ * 重置会话状态(新会话开始时调用)
99
+ */
100
+ public resetSession(): void {
101
+ console.log('[AutoRecall] 会话状态已重置');
102
+ }
103
+
104
+ /**
105
+ * 检查是否包含触发关键词
106
+ */
107
+ private hasTriggerKeyword(prompt: string): boolean {
108
+ const keywords = this.config.manualTriggerKeywords || [];
109
+ const lowerPrompt = prompt.toLowerCase();
110
+
111
+ for (const keyword of keywords) {
112
+ if (lowerPrompt.includes(keyword)) {
113
+ return true;
114
+ }
115
+ }
116
+ return false;
117
+ }
118
+
119
+ /**
120
+ * 从事件中提取用户输入
121
+ */
122
+ private extractUserPrompt(prompt: any): string {
123
+ if (typeof prompt === 'string') {
124
+ return prompt;
125
+ }
126
+
127
+ if (Array.isArray(prompt)) {
128
+ // 取最后一条用户消息
129
+ for (let i = prompt.length - 1; i >= 0; i--) {
130
+ const msg = prompt[i];
131
+ if (msg.role === 'user') {
132
+ if (typeof msg.content === 'string') {
133
+ return msg.content;
134
+ }
135
+ if (Array.isArray(msg.content)) {
136
+ for (const block of msg.content) {
137
+ if (block.type === 'text' && block.text) {
138
+ return block.text;
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ return '';
147
+ }
148
+
149
+ /**
150
+ * 格式化记忆为上下文字符串
151
+ */
152
+ private formatMemories(memories: Memory[]): string {
153
+ const lines = memories.map((m, i) => {
154
+ const date = new Date(m.createdAt).toLocaleDateString('zh-CN');
155
+ const importance = this.getImportanceEmoji(m.importance);
156
+
157
+ return `${i + 1}. ${importance} [${date}] ${m.category}/${m.subCategory}
158
+ 任务: ${m.content.task}
159
+ 结果: ${m.content.result}`;
160
+ });
161
+
162
+ return `<relevant-memories>
163
+ 以下是你之前与用户交流的相关记忆,供参考:
164
+ ${lines.join('\n\n')}
165
+ </relevant-memories>`;
166
+ }
167
+
168
+ /**
169
+ * 获取重要性emoji
170
+ */
171
+ private getImportanceEmoji(importance: string): string {
172
+ switch (importance) {
173
+ case 'critical': return '⭐';
174
+ case 'high': return '🔥';
175
+ case 'medium': return '📝';
176
+ case 'low': return '💤';
177
+ default: return '📝';
178
+ }
179
+ }
180
+ }