foliko 1.0.86 → 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/.agent/data/default.json +3 -4310
- package/.agent/data/plugins-state.json +34 -1
- package/.agent/mcp_config.json +0 -1
- package/.agent/memory/core.md +1 -0
- package/.agent/memory/project/mnn93ogy-ypjn27.md +9 -0
- package/.agent/memory/project/mnn98fqy-5nhc1u.md +25 -0
- package/.agent/memory/user/mnm67t9m-x8rekk.md +9 -0
- package/.agent/memory/user/mnn5mmqh-w6aktx.md +11 -0
- package/.agent/memory/user/mnnbfhhn-dk1bd1.md +22 -0
- package/.agent/plugins/__pycache__/file_writer.cpython-312.pyc +0 -0
- package/.agent/plugins/marknative/index.js +2 -7
- package/.agent/plugins/poster-plugin/README.md +304 -0
- package/.agent/plugins/poster-plugin/fonts/PatuaOne-Regular.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221/347/262/227/344/275/223.ttf +0 -0
- package/.agent/plugins/poster-plugin/index.js +13 -0
- package/.agent/plugins/poster-plugin/package.json +28 -0
- package/.agent/plugins/poster-plugin/src/canvas.js +161 -0
- package/.agent/plugins/poster-plugin/src/components/arrow.js +84 -0
- package/.agent/plugins/poster-plugin/src/components/avatar.js +71 -0
- package/.agent/plugins/poster-plugin/src/components/badge.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/card.js +88 -0
- package/.agent/plugins/poster-plugin/src/components/chart.js +127 -0
- package/.agent/plugins/poster-plugin/src/components/chip.js +88 -0
- package/.agent/plugins/poster-plugin/src/components/columns.js +107 -0
- package/.agent/plugins/poster-plugin/src/components/cta.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/divider.js +55 -0
- package/.agent/plugins/poster-plugin/src/components/feature.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/featureGrid.js +112 -0
- package/.agent/plugins/poster-plugin/src/components/grid.js +118 -0
- package/.agent/plugins/poster-plugin/src/components/imageFrame.js +155 -0
- package/.agent/plugins/poster-plugin/src/components/index.js +62 -0
- package/.agent/plugins/poster-plugin/src/components/listItem.js +146 -0
- package/.agent/plugins/poster-plugin/src/components/notification.js +123 -0
- package/.agent/plugins/poster-plugin/src/components/progress.js +79 -0
- package/.agent/plugins/poster-plugin/src/components/progressCircle.js +117 -0
- package/.agent/plugins/poster-plugin/src/components/quote.js +97 -0
- package/.agent/plugins/poster-plugin/src/components/rating.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/star.js +70 -0
- package/.agent/plugins/poster-plugin/src/components/statCard.js +105 -0
- package/.agent/plugins/poster-plugin/src/components/stepper.js +118 -0
- package/.agent/plugins/poster-plugin/src/components/table.js +159 -0
- package/.agent/plugins/poster-plugin/src/components/tagCloud.js +78 -0
- package/.agent/plugins/poster-plugin/src/components/timeline.js +105 -0
- package/.agent/plugins/poster-plugin/src/components/watermark.js +52 -0
- package/.agent/plugins/poster-plugin/src/composer.js +1904 -0
- package/.agent/plugins/poster-plugin/src/elements/artText.js +60 -0
- package/.agent/plugins/poster-plugin/src/elements/background.js +52 -0
- package/.agent/plugins/poster-plugin/src/elements/circle.js +31 -0
- package/.agent/plugins/poster-plugin/src/elements/image.js +71 -0
- package/.agent/plugins/poster-plugin/src/elements/index.js +26 -0
- package/.agent/plugins/poster-plugin/src/elements/line.js +23 -0
- package/.agent/plugins/poster-plugin/src/elements/polygon.js +32 -0
- package/.agent/plugins/poster-plugin/src/elements/rectangle.js +32 -0
- package/.agent/plugins/poster-plugin/src/elements/svg.js +92 -0
- package/.agent/plugins/poster-plugin/src/elements/text.js +38 -0
- package/.agent/plugins/poster-plugin/src/fonts.js +118 -0
- package/.agent/plugins/poster-plugin/src/index.js +1659 -0
- package/.agent/plugins/poster-plugin/src/presets.js +36 -0
- package/.agent/plugins/poster-plugin/src/templates/business.js +60 -0
- package/.agent/plugins/poster-plugin/src/templates/gradient.js +64 -0
- package/.agent/plugins/poster-plugin/src/templates/index.js +43 -0
- package/.agent/plugins/poster-plugin/src/templates/modern.js +69 -0
- package/.agent/plugins/poster-plugin/src/templates/simple.js +58 -0
- package/.agent/plugins/poster-plugin/src/templates/social.js +62 -0
- package/.agent/plugins/poster-plugin/src/templates/tech.js +84 -0
- package/.agent/sessions/cli_default.json +24265 -0
- package/.agent/weixin.json +6 -0
- package/.claude/settings.local.json +176 -171
- package/CLAUDE.md +144 -108
- package/docs/CONTEXT_DESIGN.md +1596 -0
- package/examples/test-concurrent-chat.js +60 -0
- package/examples/test-long-chat.js +77 -0
- package/examples/test-session-chat.js +93 -0
- package/output/beef-love-poster.png +0 -0
- package/package.json +2 -2
- package/plugins/default-plugins.js +2 -1
- package/plugins/extension-executor-plugin.js +11 -0
- package/plugins/memory-plugin.js +984 -0
- package/plugins/session-plugin.js +78 -1
- package/plugins/weixin-plugin.js +24 -22
- package/skills/poster-guide/SKILL.md +743 -0
- package/skills/python-plugin-dev/SKILL.md +238 -238
- package/skills/skill-guide/SKILL.md +130 -108
- package/src/capabilities/skill-manager.js +99 -0
- package/src/core/agent-chat.js +627 -180
- package/src/core/agent-context.js +188 -0
- package/src/core/agent.js +9 -63
- package/src/core/context-manager.js +283 -0
- package/src/core/framework.js +264 -3
- package/src/core/plugin-manager.js +79 -2
- package/src/core/request-context.js +98 -0
- package/src/core/session-context.js +341 -0
- package/src/core/session-storage.js +274 -0
- package/src/executors/mcp-executor.js +2 -2
- package/src/utils/index.js +239 -67
- package/src/utils/plugin-helpers.js +17 -0
- package//346/265/267/346/212/245/346/217/222/344/273/266.md +621 -0
- package/.agent/plugins/__pycache__/test_plugin.cpython-312.pyc +0 -0
- package/.agent/plugins/temp-repo/LICENSE +0 -201
- package/.agent/plugins/temp-repo/puppeteer-plugin/README.md +0 -147
- package/.agent/plugins/temp-repo/puppeteer-plugin/index.js +0 -1418
- package/.agent/plugins/temp-repo/puppeteer-plugin/package.json +0 -9
- package/.agent/plugins/test_plugin.py +0 -304
|
@@ -0,0 +1,984 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory 记忆插件
|
|
3
|
+
* 四层记忆系统:user, feedback, project, reference
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Plugin } = require('../src/core/plugin-base')
|
|
7
|
+
const { logger } = require('../src/utils/logger')
|
|
8
|
+
const log = logger.child('Memory')
|
|
9
|
+
const { z } = require('zod')
|
|
10
|
+
const fs = require('fs')
|
|
11
|
+
const path = require('path')
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 记忆类型常量
|
|
15
|
+
*/
|
|
16
|
+
const MEMORY_TYPES = {
|
|
17
|
+
USER: 'user',
|
|
18
|
+
FEEDBACK: 'feedback',
|
|
19
|
+
PROJECT: 'project',
|
|
20
|
+
REFERENCE: 'reference'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 记忆存储路径
|
|
25
|
+
*/
|
|
26
|
+
const MEMORY_BASE_DIR = '.agent/memory'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 解析 YAML frontmatter(从 skill-manager 复用)
|
|
30
|
+
*/
|
|
31
|
+
function parseFrontmatter(content) {
|
|
32
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
|
|
33
|
+
if (!match) return null
|
|
34
|
+
|
|
35
|
+
const frontmatter = {}
|
|
36
|
+
const lines = match[1].split('\n')
|
|
37
|
+
let currentKey = null
|
|
38
|
+
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
if (currentKey === 'metadata') {
|
|
41
|
+
if (line.match(/^ {2}[a-zA-Z]/)) {
|
|
42
|
+
const colonIndex = line.indexOf(':')
|
|
43
|
+
if (colonIndex > 0) {
|
|
44
|
+
const key = line.substring(2, colonIndex).trim()
|
|
45
|
+
const value = line.substring(colonIndex + 1).trim().replace(/^["']|["']$/g, '')
|
|
46
|
+
frontmatter[currentKey][key] = value
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
} else if (line.match(/^[a-zA-Z]/)) {
|
|
50
|
+
currentKey = null
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const colonIndex = line.indexOf(':')
|
|
56
|
+
if (colonIndex === -1) continue
|
|
57
|
+
|
|
58
|
+
const key = line.substring(0, colonIndex).trim()
|
|
59
|
+
let value = line.substring(colonIndex + 1).trim()
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
63
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
64
|
+
) {
|
|
65
|
+
value = value.slice(1, -1)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (key === 'metadata') {
|
|
69
|
+
currentKey = 'metadata'
|
|
70
|
+
frontmatter.metadata = {}
|
|
71
|
+
} else if (key === 'tags') {
|
|
72
|
+
frontmatter[key] = value
|
|
73
|
+
.split(',')
|
|
74
|
+
.map(t => t.trim().replace(/^["']|["']$/g, ''))
|
|
75
|
+
.filter(t => t)
|
|
76
|
+
} else {
|
|
77
|
+
frontmatter[key] = value
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return frontmatter
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 序列化 frontmatter
|
|
86
|
+
*/
|
|
87
|
+
function serializeFrontmatter(data) {
|
|
88
|
+
const lines = ['---']
|
|
89
|
+
for (const [key, value] of Object.entries(data)) {
|
|
90
|
+
if (key === 'metadata') {
|
|
91
|
+
lines.push('metadata:')
|
|
92
|
+
for (const [mk, mv] of Object.entries(value)) {
|
|
93
|
+
lines.push(` ${mk}: "${mv}"`)
|
|
94
|
+
}
|
|
95
|
+
} else if (Array.isArray(value)) {
|
|
96
|
+
lines.push(`${key}: [${value.join(', ')}]`)
|
|
97
|
+
} else {
|
|
98
|
+
lines.push(`${key}: "${value}"`)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
lines.push('---')
|
|
102
|
+
return lines.join('\n')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 路径安全检查
|
|
107
|
+
*/
|
|
108
|
+
function sanitizeMemoryPath(relativePath, baseDir) {
|
|
109
|
+
// 拒绝绝对路径
|
|
110
|
+
if (path.isAbsolute(relativePath)) {
|
|
111
|
+
throw new Error('Absolute paths are not allowed')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 拒绝包含 .. 的路径
|
|
115
|
+
if (relativePath.includes('..')) {
|
|
116
|
+
throw new Error('Parent directory references are not allowed')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 解析并验证最终路径在 baseDir 内
|
|
120
|
+
const resolved = path.resolve(baseDir, relativePath)
|
|
121
|
+
const baseResolved = path.resolve(baseDir)
|
|
122
|
+
|
|
123
|
+
if (!resolved.startsWith(baseResolved)) {
|
|
124
|
+
throw new Error('Path outside memory directory is not allowed')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return resolved
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 生成记忆 ID
|
|
132
|
+
*/
|
|
133
|
+
function generateMemoryId() {
|
|
134
|
+
const timestamp = Date.now().toString(36)
|
|
135
|
+
const random = Math.random().toString(36).substring(2, 8)
|
|
136
|
+
return `${timestamp}-${random}`
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 从文本中提取 JSON 对象(处理嵌套括号)
|
|
141
|
+
* @param {string} text - 输入文本
|
|
142
|
+
* @returns {string|null} 提取的 JSON 字符串,失败返回 null
|
|
143
|
+
*/
|
|
144
|
+
function _extractJSON(text) {
|
|
145
|
+
if (!text || typeof text !== 'string') return null
|
|
146
|
+
|
|
147
|
+
// 去掉首尾空白
|
|
148
|
+
text = text.trim()
|
|
149
|
+
if (!text.startsWith('{')) return null
|
|
150
|
+
|
|
151
|
+
// 查找配对的括号位置
|
|
152
|
+
let braceCount = 0
|
|
153
|
+
let inString = false
|
|
154
|
+
let escaped = false
|
|
155
|
+
|
|
156
|
+
for (let i = 0; i < text.length; i++) {
|
|
157
|
+
const char = text[i]
|
|
158
|
+
|
|
159
|
+
if (escaped) {
|
|
160
|
+
escaped = false
|
|
161
|
+
continue
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (char === '\\' && inString) {
|
|
165
|
+
escaped = true
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (char === '"') {
|
|
170
|
+
inString = !inString
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!inString) {
|
|
175
|
+
if (char === '{') {
|
|
176
|
+
braceCount++
|
|
177
|
+
} else if (char === '}') {
|
|
178
|
+
braceCount--
|
|
179
|
+
if (braceCount === 0) {
|
|
180
|
+
// 找到完整的 JSON 对象
|
|
181
|
+
return text.substring(0, i + 1)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return null
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* MemoryStore - 双层存储(内存 + 文件)
|
|
192
|
+
*/
|
|
193
|
+
class MemoryStore {
|
|
194
|
+
constructor(baseDir) {
|
|
195
|
+
this._baseDir = path.resolve(process.cwd(), baseDir)
|
|
196
|
+
this._memory = new Map() // memoryId -> memory object
|
|
197
|
+
this._indexByType = new Map() // type -> Set of memoryIds
|
|
198
|
+
this._indexByProject = new Map() // project -> Set of memoryIds
|
|
199
|
+
this._fileIndex = new Map() // memoryId -> filePath
|
|
200
|
+
|
|
201
|
+
// 初始化目录结构
|
|
202
|
+
this._initDirs()
|
|
203
|
+
// 加载已有记忆
|
|
204
|
+
this._loadAll()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_initDirs() {
|
|
208
|
+
const dirs = [
|
|
209
|
+
this._baseDir,
|
|
210
|
+
path.join(this._baseDir, 'user'),
|
|
211
|
+
path.join(this._baseDir, 'feedback'),
|
|
212
|
+
path.join(this._baseDir, 'project'),
|
|
213
|
+
path.join(this._baseDir, 'reference')
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
for (const dir of dirs) {
|
|
217
|
+
if (!fs.existsSync(dir)) {
|
|
218
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
_getTypeDir(type) {
|
|
224
|
+
return path.join(this._baseDir, type)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
_loadAll() {
|
|
228
|
+
for (const type of Object.values(MEMORY_TYPES)) {
|
|
229
|
+
const typeDir = this._getTypeDir(type)
|
|
230
|
+
if (!fs.existsSync(typeDir)) continue
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const files = fs.readdirSync(typeDir)
|
|
234
|
+
for (const file of files) {
|
|
235
|
+
if (!file.endsWith('.md')) continue
|
|
236
|
+
const filePath = path.join(typeDir, file)
|
|
237
|
+
try {
|
|
238
|
+
const memory = this._loadFromFile(filePath)
|
|
239
|
+
if (memory && memory.id) {
|
|
240
|
+
this._addToIndexes(memory)
|
|
241
|
+
this._fileIndex.set(memory.id, filePath)
|
|
242
|
+
}
|
|
243
|
+
} catch (err) {
|
|
244
|
+
log.warn(`Failed to load memory from ${filePath}:`, err.message)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} catch (err) {
|
|
248
|
+
log.warn(`Failed to read memory directory ${typeDir}:`, err.message)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
log.info(`Loaded ${this._memory.size} memories from disk`)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
_loadFromFile(filePath) {
|
|
256
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
257
|
+
const frontmatter = parseFrontmatter(content)
|
|
258
|
+
|
|
259
|
+
if (!frontmatter) {
|
|
260
|
+
return null
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 移除 frontmatter 获取正文
|
|
264
|
+
const contentMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/)
|
|
265
|
+
const body = contentMatch ? contentMatch[1].trim() : content
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
id: frontmatter.id || path.basename(filePath, '.md'),
|
|
269
|
+
name: frontmatter.name || '',
|
|
270
|
+
type: frontmatter.type || 'user',
|
|
271
|
+
project: frontmatter.project || null,
|
|
272
|
+
tags: frontmatter.tags || [],
|
|
273
|
+
created: frontmatter.created || new Date().toISOString().split('T')[0],
|
|
274
|
+
body
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
_saveToFile(memory) {
|
|
279
|
+
const typeDir = this._getTypeDir(memory.type)
|
|
280
|
+
const fileName = `${memory.id}.md`
|
|
281
|
+
const filePath = path.join(typeDir, fileName)
|
|
282
|
+
|
|
283
|
+
const frontmatter = {
|
|
284
|
+
id: memory.id,
|
|
285
|
+
name: memory.name,
|
|
286
|
+
type: memory.type,
|
|
287
|
+
project: memory.project,
|
|
288
|
+
tags: memory.tags,
|
|
289
|
+
created: memory.created
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const content = serializeFrontmatter(frontmatter) + '\n' + memory.body
|
|
293
|
+
fs.writeFileSync(filePath, content, 'utf-8')
|
|
294
|
+
return filePath
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
_addToIndexes(memory) {
|
|
298
|
+
this._memory.set(memory.id, memory)
|
|
299
|
+
|
|
300
|
+
// 按类型索引
|
|
301
|
+
if (!this._indexByType.has(memory.type)) {
|
|
302
|
+
this._indexByType.set(memory.type, new Set())
|
|
303
|
+
}
|
|
304
|
+
this._indexByType.get(memory.type).add(memory.id)
|
|
305
|
+
|
|
306
|
+
// 按项目索引
|
|
307
|
+
if (memory.project) {
|
|
308
|
+
if (!this._indexByProject.has(memory.project)) {
|
|
309
|
+
this._indexByProject.set(memory.project, new Set())
|
|
310
|
+
}
|
|
311
|
+
this._indexByProject.get(memory.project).add(memory.id)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
_removeFromIndexes(memory) {
|
|
316
|
+
this._memory.delete(memory.id)
|
|
317
|
+
|
|
318
|
+
const typeSet = this._indexByType.get(memory.type)
|
|
319
|
+
if (typeSet) typeSet.delete(memory.id)
|
|
320
|
+
|
|
321
|
+
if (memory.project) {
|
|
322
|
+
const projectSet = this._indexByProject.get(memory.project)
|
|
323
|
+
if (projectSet) projectSet.delete(memory.id)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 公开 API
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* 添加记忆
|
|
331
|
+
*/
|
|
332
|
+
add(memory) {
|
|
333
|
+
const id = memory.id || generateMemoryId()
|
|
334
|
+
const now = new Date().toISOString().split('T')[0]
|
|
335
|
+
|
|
336
|
+
const newMemory = {
|
|
337
|
+
id,
|
|
338
|
+
name: memory.name || '',
|
|
339
|
+
type: memory.type || MEMORY_TYPES.USER,
|
|
340
|
+
project: memory.project || null,
|
|
341
|
+
tags: memory.tags || [],
|
|
342
|
+
created: memory.created || now,
|
|
343
|
+
body: memory.body || ''
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 验证类型
|
|
347
|
+
if (!Object.values(MEMORY_TYPES).includes(newMemory.type)) {
|
|
348
|
+
throw new Error(`Invalid memory type: ${newMemory.type}`)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const filePath = this._saveToFile(newMemory)
|
|
352
|
+
this._addToIndexes(newMemory)
|
|
353
|
+
this._fileIndex.set(id, filePath)
|
|
354
|
+
|
|
355
|
+
log.debug(`Added memory: ${id} (${newMemory.type})`)
|
|
356
|
+
return newMemory
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* 获取记忆
|
|
361
|
+
*/
|
|
362
|
+
get(id) {
|
|
363
|
+
return this._memory.get(id) || null
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* 更新记忆
|
|
368
|
+
*/
|
|
369
|
+
update(id, updates) {
|
|
370
|
+
const memory = this._memory.get(id)
|
|
371
|
+
if (!memory) {
|
|
372
|
+
throw new Error(`Memory not found: ${id}`)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const oldType = memory.type
|
|
376
|
+
const oldProject = memory.project
|
|
377
|
+
|
|
378
|
+
// 应用更新
|
|
379
|
+
if (updates.name !== undefined) memory.name = updates.name
|
|
380
|
+
if (updates.type !== undefined) memory.type = updates.type
|
|
381
|
+
if (updates.project !== undefined) memory.project = updates.project
|
|
382
|
+
if (updates.tags !== undefined) memory.tags = updates.tags
|
|
383
|
+
if (updates.body !== undefined) memory.body = updates.body
|
|
384
|
+
|
|
385
|
+
// 如果类型或项目改变,需要重建索引
|
|
386
|
+
if (oldType !== memory.type || oldProject !== memory.project) {
|
|
387
|
+
this._removeFromIndexes({ ...memory, type: oldType, project: oldProject })
|
|
388
|
+
this._addToIndexes(memory)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// 重新保存文件
|
|
392
|
+
const filePath = this._saveToFile(memory)
|
|
393
|
+
this._fileIndex.set(id, filePath)
|
|
394
|
+
|
|
395
|
+
log.debug(`Updated memory: ${id}`)
|
|
396
|
+
return memory
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* 删除记忆
|
|
401
|
+
*/
|
|
402
|
+
delete(id) {
|
|
403
|
+
const memory = this._memory.get(id)
|
|
404
|
+
if (!memory) {
|
|
405
|
+
return false
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// 删除文件
|
|
409
|
+
const filePath = this._fileIndex.get(id)
|
|
410
|
+
if (filePath && fs.existsSync(filePath)) {
|
|
411
|
+
fs.unlinkSync(filePath)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
this._removeFromIndexes(memory)
|
|
415
|
+
this._fileIndex.delete(id)
|
|
416
|
+
|
|
417
|
+
log.debug(`Deleted memory: ${id}`)
|
|
418
|
+
return true
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* 搜索记忆
|
|
423
|
+
*/
|
|
424
|
+
search(query, options = {}) {
|
|
425
|
+
const { type, project, tags, limit = 50 } = options
|
|
426
|
+
let results = []
|
|
427
|
+
|
|
428
|
+
// 根据类型筛选
|
|
429
|
+
if (type) {
|
|
430
|
+
const typeSet = this._indexByType.get(type)
|
|
431
|
+
if (typeSet) {
|
|
432
|
+
results = Array.from(typeSet).map(id => this._memory.get(id)).filter(Boolean)
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
results = Array.from(this._memory.values())
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// 根据项目筛选
|
|
439
|
+
if (project) {
|
|
440
|
+
results = results.filter(m => m.project === project)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// 根据标签筛选
|
|
444
|
+
if (tags && tags.length > 0) {
|
|
445
|
+
results = results.filter(m =>
|
|
446
|
+
tags.some(tag => m.tags && m.tags.includes(tag))
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// 根据查询字符串搜索(名称 + 正文)
|
|
451
|
+
if (query && query.trim()) {
|
|
452
|
+
const lowerQuery = query.toLowerCase()
|
|
453
|
+
results = results.filter(m =>
|
|
454
|
+
m.name.toLowerCase().includes(lowerQuery) ||
|
|
455
|
+
m.body.toLowerCase().includes(lowerQuery)
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// 限制返回数量
|
|
460
|
+
return results.slice(0, limit)
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* 列出指定类型的所有记忆
|
|
465
|
+
*/
|
|
466
|
+
listByType(type, limit = 100) {
|
|
467
|
+
const typeSet = this._indexByType.get(type)
|
|
468
|
+
if (!typeSet) return []
|
|
469
|
+
return Array.from(typeSet)
|
|
470
|
+
.map(id => this._memory.get(id))
|
|
471
|
+
.filter(Boolean)
|
|
472
|
+
.slice(0, limit)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* 按项目列出记忆
|
|
477
|
+
*/
|
|
478
|
+
listByProject(project, limit = 100) {
|
|
479
|
+
const projectSet = this._indexByProject.get(project)
|
|
480
|
+
if (!projectSet) return []
|
|
481
|
+
return Array.from(projectSet)
|
|
482
|
+
.map(id => this._memory.get(id))
|
|
483
|
+
.filter(Boolean)
|
|
484
|
+
.slice(0, limit)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* 获取所有项目列表
|
|
489
|
+
*/
|
|
490
|
+
getProjects() {
|
|
491
|
+
return Array.from(this._indexByProject.keys())
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* 获取统计信息
|
|
496
|
+
*/
|
|
497
|
+
getStats() {
|
|
498
|
+
const stats = {}
|
|
499
|
+
for (const type of Object.values(MEMORY_TYPES)) {
|
|
500
|
+
const typeSet = this._indexByType.get(type)
|
|
501
|
+
stats[type] = typeSet ? typeSet.size : 0
|
|
502
|
+
}
|
|
503
|
+
stats.total = this._memory.size
|
|
504
|
+
return stats
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* MemoryPlugin
|
|
510
|
+
*/
|
|
511
|
+
class MemoryPlugin extends Plugin {
|
|
512
|
+
constructor(config = {}) {
|
|
513
|
+
super()
|
|
514
|
+
this.name = 'memory'
|
|
515
|
+
this.version = '1.0.0'
|
|
516
|
+
this.description = '四层记忆系统,支持 user/feedback/project/reference 四种记忆类型'
|
|
517
|
+
this.priority = 6 // 在 session 之后加载
|
|
518
|
+
|
|
519
|
+
this.system = true
|
|
520
|
+
|
|
521
|
+
this.config = {
|
|
522
|
+
memoryDir: config.memoryDir || MEMORY_BASE_DIR,
|
|
523
|
+
autoExtract: config.autoExtract !== false, // 默认开启自动提取
|
|
524
|
+
extractOnTopics: ['provider', 'preference', 'setting', 'user'] // 触发自动提取的关键词
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
this._framework = null
|
|
528
|
+
this._store = null
|
|
529
|
+
this._memoryAgent = null // 用于记忆提取的子 agent
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
install(framework) {
|
|
533
|
+
this._framework = framework
|
|
534
|
+
return this
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
start(framework) {
|
|
538
|
+
// 初始化存储
|
|
539
|
+
this._store = new MemoryStore(this.config.memoryDir)
|
|
540
|
+
|
|
541
|
+
// 注册工具
|
|
542
|
+
this._registerTools(framework)
|
|
543
|
+
|
|
544
|
+
// 设置自动记忆提取(监听对话结束事件)
|
|
545
|
+
if (this.config.autoExtract) {
|
|
546
|
+
this._setupAutoExtract(framework)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// 注册 memory-context 到系统提示(优先级 350)
|
|
550
|
+
this._registerMemoryContext(framework)
|
|
551
|
+
|
|
552
|
+
log.info('Memory plugin started')
|
|
553
|
+
return this
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
_registerTools(framework) {
|
|
557
|
+
// memory_search - 搜索记忆
|
|
558
|
+
framework.registerTool({
|
|
559
|
+
name: 'memory_search',
|
|
560
|
+
description: '搜索记忆库,支持按类型、项目、标签搜索',
|
|
561
|
+
inputSchema: z.object({
|
|
562
|
+
query: z.string().optional().describe('搜索关键词'),
|
|
563
|
+
type: z.enum(['user', 'feedback', 'project', 'reference']).optional().describe('记忆类型'),
|
|
564
|
+
project: z.string().optional().describe('项目名称'),
|
|
565
|
+
tags: z.array(z.string()).optional().describe('标签列表'),
|
|
566
|
+
limit: z.number().optional().describe('返回数量限制,默认 50')
|
|
567
|
+
}),
|
|
568
|
+
execute: async (args) => {
|
|
569
|
+
try {
|
|
570
|
+
const results = this._store.search(args.query, {
|
|
571
|
+
type: args.type,
|
|
572
|
+
project: args.project,
|
|
573
|
+
tags: args.tags,
|
|
574
|
+
limit: args.limit || 50
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
return {
|
|
578
|
+
success: true,
|
|
579
|
+
count: results.length,
|
|
580
|
+
memories: results.map(m => ({
|
|
581
|
+
id: m.id,
|
|
582
|
+
name: m.name,
|
|
583
|
+
type: m.type,
|
|
584
|
+
project: m.project,
|
|
585
|
+
tags: m.tags,
|
|
586
|
+
created: m.created,
|
|
587
|
+
preview: m.body.substring(0, 200)
|
|
588
|
+
}))
|
|
589
|
+
}
|
|
590
|
+
} catch (err) {
|
|
591
|
+
return { success: false, error: err.message }
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
// memory_write - 写入记忆
|
|
597
|
+
framework.registerTool({
|
|
598
|
+
name: 'memory_write',
|
|
599
|
+
description: '写入新的记忆,支持 YAML frontmatter',
|
|
600
|
+
inputSchema: z.object({
|
|
601
|
+
name: z.string().describe('记忆名称'),
|
|
602
|
+
type: z.enum(['user', 'feedback', 'project', 'reference']).describe('记忆类型'),
|
|
603
|
+
project: z.string().optional().describe('关联项目'),
|
|
604
|
+
tags: z.array(z.string()).optional().describe('标签'),
|
|
605
|
+
content: z.string().describe('记忆内容')
|
|
606
|
+
}),
|
|
607
|
+
execute: async (args) => {
|
|
608
|
+
try {
|
|
609
|
+
const memory = this._store.add({
|
|
610
|
+
name: args.name,
|
|
611
|
+
type: args.type,
|
|
612
|
+
project: args.project || null,
|
|
613
|
+
tags: args.tags || [],
|
|
614
|
+
body: args.content
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
return {
|
|
618
|
+
success: true,
|
|
619
|
+
id: memory.id,
|
|
620
|
+
message: `Memory created: ${memory.id}`
|
|
621
|
+
}
|
|
622
|
+
} catch (err) {
|
|
623
|
+
return { success: false, error: err.message }
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
// memory_list - 列出记忆
|
|
629
|
+
framework.registerTool({
|
|
630
|
+
name: 'memory_list',
|
|
631
|
+
description: '列出指定类型的记忆',
|
|
632
|
+
inputSchema: z.object({
|
|
633
|
+
type: z.enum(['user', 'feedback', 'project', 'reference']).describe('记忆类型'),
|
|
634
|
+
project: z.string().optional().describe('项目名称(当类型为 project 时)'),
|
|
635
|
+
limit: z.number().optional().describe('返回数量限制,默认 100')
|
|
636
|
+
}),
|
|
637
|
+
execute: async (args) => {
|
|
638
|
+
try {
|
|
639
|
+
let memories
|
|
640
|
+
if (args.type === 'project' && args.project) {
|
|
641
|
+
memories = this._store.listByProject(args.project, args.limit || 100)
|
|
642
|
+
} else {
|
|
643
|
+
memories = this._store.listByType(args.type, args.limit || 100)
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return {
|
|
647
|
+
success: true,
|
|
648
|
+
count: memories.length,
|
|
649
|
+
memories: memories.map(m => ({
|
|
650
|
+
id: m.id,
|
|
651
|
+
name: m.name,
|
|
652
|
+
type: m.type,
|
|
653
|
+
project: m.project,
|
|
654
|
+
tags: m.tags,
|
|
655
|
+
created: m.created,
|
|
656
|
+
preview: m.body.substring(0, 200)
|
|
657
|
+
}))
|
|
658
|
+
}
|
|
659
|
+
} catch (err) {
|
|
660
|
+
return { success: false, error: err.message }
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
// memory_delete - 删除记忆
|
|
666
|
+
framework.registerTool({
|
|
667
|
+
name: 'memory_delete',
|
|
668
|
+
description: '删除指定记忆',
|
|
669
|
+
inputSchema: z.object({
|
|
670
|
+
id: z.string().describe('记忆 ID')
|
|
671
|
+
}),
|
|
672
|
+
execute: async (args) => {
|
|
673
|
+
try {
|
|
674
|
+
const deleted = this._store.delete(args.id)
|
|
675
|
+
return { success: deleted, deleted: args.id }
|
|
676
|
+
} catch (err) {
|
|
677
|
+
return { success: false, error: err.message }
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
})
|
|
681
|
+
|
|
682
|
+
// memory_update - 更新记忆
|
|
683
|
+
framework.registerTool({
|
|
684
|
+
name: 'memory_update',
|
|
685
|
+
description: '更新已有记忆',
|
|
686
|
+
inputSchema: z.object({
|
|
687
|
+
id: z.string().describe('记忆 ID'),
|
|
688
|
+
name: z.string().optional().describe('新名称'),
|
|
689
|
+
type: z.enum(['user', 'feedback', 'project', 'reference']).optional().describe('新类型'),
|
|
690
|
+
project: z.string().optional().describe('新关联项目'),
|
|
691
|
+
tags: z.array(z.string()).optional().describe('新标签'),
|
|
692
|
+
content: z.string().optional().describe('新内容')
|
|
693
|
+
}),
|
|
694
|
+
execute: async (args) => {
|
|
695
|
+
try {
|
|
696
|
+
const updates = {}
|
|
697
|
+
if (args.name !== undefined) updates.name = args.name
|
|
698
|
+
if (args.type !== undefined) updates.type = args.type
|
|
699
|
+
if (args.project !== undefined) updates.project = args.project
|
|
700
|
+
if (args.tags !== undefined) updates.tags = args.tags
|
|
701
|
+
if (args.content !== undefined) updates.body = args.content
|
|
702
|
+
|
|
703
|
+
const memory = this._store.update(args.id, updates)
|
|
704
|
+
return {
|
|
705
|
+
success: true,
|
|
706
|
+
id: memory.id,
|
|
707
|
+
message: `Memory updated: ${memory.id}`
|
|
708
|
+
}
|
|
709
|
+
} catch (err) {
|
|
710
|
+
return { success: false, error: err.message }
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
})
|
|
714
|
+
|
|
715
|
+
// memory_get - 获取单条记忆详情
|
|
716
|
+
framework.registerTool({
|
|
717
|
+
name: 'memory_get',
|
|
718
|
+
description: '获取单条记忆的完整内容',
|
|
719
|
+
inputSchema: z.object({
|
|
720
|
+
id: z.string().describe('记忆 ID')
|
|
721
|
+
}),
|
|
722
|
+
execute: async (args) => {
|
|
723
|
+
try {
|
|
724
|
+
const memory = this._store.get(args.id)
|
|
725
|
+
if (!memory) {
|
|
726
|
+
return { success: false, error: 'Memory not found' }
|
|
727
|
+
}
|
|
728
|
+
return {
|
|
729
|
+
success: true,
|
|
730
|
+
memory: {
|
|
731
|
+
id: memory.id,
|
|
732
|
+
name: memory.name,
|
|
733
|
+
type: memory.type,
|
|
734
|
+
project: memory.project,
|
|
735
|
+
tags: memory.tags,
|
|
736
|
+
created: memory.created,
|
|
737
|
+
content: memory.body
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
} catch (err) {
|
|
741
|
+
return { success: false, error: err.message }
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
})
|
|
745
|
+
|
|
746
|
+
// memory_stats - 获取记忆统计
|
|
747
|
+
framework.registerTool({
|
|
748
|
+
name: 'memory_stats',
|
|
749
|
+
description: '获取记忆统计信息',
|
|
750
|
+
inputSchema: z.object({}),
|
|
751
|
+
execute: async () => {
|
|
752
|
+
try {
|
|
753
|
+
const stats = this._store.getStats()
|
|
754
|
+
const projects = this._store.getProjects()
|
|
755
|
+
return {
|
|
756
|
+
success: true,
|
|
757
|
+
stats,
|
|
758
|
+
projects
|
|
759
|
+
}
|
|
760
|
+
} catch (err) {
|
|
761
|
+
return { success: false, error: err.message }
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
})
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* 设置自动记忆提取
|
|
769
|
+
*/
|
|
770
|
+
_setupAutoExtract(framework) {
|
|
771
|
+
// 监听 agent 消息完成事件,在对话轮次结束时提取记忆
|
|
772
|
+
framework.on('agent:message', async ({ userMessage, assistantResponse }) => {
|
|
773
|
+
try {
|
|
774
|
+
await this._extractMemoryFromConversation(userMessage, assistantResponse)
|
|
775
|
+
} catch (err) {
|
|
776
|
+
log.warn('Auto memory extraction failed:', err.message)
|
|
777
|
+
}
|
|
778
|
+
})
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* 从对话中提取记忆
|
|
783
|
+
*/
|
|
784
|
+
async _extractMemoryFromConversation(userMessage, assistantResponse) {
|
|
785
|
+
// 构建提取提示
|
|
786
|
+
const extractPrompt = `你是一个记忆提取专家。请分析以下对话,提取有价值的信息到记忆系统。
|
|
787
|
+
|
|
788
|
+
用户消息:
|
|
789
|
+
${typeof userMessage === 'string' ? userMessage : JSON.stringify(userMessage)}
|
|
790
|
+
|
|
791
|
+
助手回复:
|
|
792
|
+
${typeof assistantResponse === 'string' ? assistantResponse : JSON.stringify(assistantResponse)}
|
|
793
|
+
|
|
794
|
+
请分析并决定是否需要提取记忆。记忆类型:
|
|
795
|
+
- user: 用户偏好、习惯、设置(如喜欢用 deepseek provider)
|
|
796
|
+
- feedback: 用户反馈、建议、批评
|
|
797
|
+
- project: 项目相关信息、上下文
|
|
798
|
+
- reference: 外部知识、参考资料
|
|
799
|
+
|
|
800
|
+
如果需要提取,请按以下格式返回(仅返回 JSON,不要有其他内容):
|
|
801
|
+
{
|
|
802
|
+
"extract": true/false,
|
|
803
|
+
"type": "user/feedback/project/reference",
|
|
804
|
+
"name": "记忆名称",
|
|
805
|
+
"project": "项目名(仅 project 类型需要)",
|
|
806
|
+
"tags": ["tag1", "tag2"],
|
|
807
|
+
"content": "要记忆的内容,包含 Why(为什么重要)和 How to apply(如何使用)"
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
如果不需要提取,返回:
|
|
811
|
+
{"extract": false}`
|
|
812
|
+
|
|
813
|
+
try {
|
|
814
|
+
// 使用 memory-subagent 执行提取(受限工具权限)
|
|
815
|
+
const result = await this._runMemoryExtractionAgent(extractPrompt)
|
|
816
|
+
|
|
817
|
+
if (result && result.extract === true) {
|
|
818
|
+
this._store.add({
|
|
819
|
+
name: result.name,
|
|
820
|
+
type: result.type,
|
|
821
|
+
project: result.project || null,
|
|
822
|
+
tags: result.tags || [],
|
|
823
|
+
body: result.content
|
|
824
|
+
})
|
|
825
|
+
log.info(`Auto-extracted memory: ${result.name} (${result.type})`)
|
|
826
|
+
}
|
|
827
|
+
} catch (err) {
|
|
828
|
+
log.warn('Memory extraction agent failed:', err.message)
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* 运行记忆提取子 agent
|
|
834
|
+
* 使用独立的轻量子 agent 处理记忆提取,不占用主 agent 资源
|
|
835
|
+
*/
|
|
836
|
+
async _runMemoryExtractionAgent(prompt) {
|
|
837
|
+
try {
|
|
838
|
+
// 获取 ai 插件获取模型配置
|
|
839
|
+
const aiPlugin = this._framework.pluginManager.get('ai')
|
|
840
|
+
if (!aiPlugin) {
|
|
841
|
+
log.warn('Memory extraction: AI plugin not found')
|
|
842
|
+
return null
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const aiClient = aiPlugin.getAIClient()
|
|
846
|
+
if (!aiClient) {
|
|
847
|
+
log.warn('Memory extraction: AI client not found')
|
|
848
|
+
return null
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// 直接使用 generateText,设置 maxSteps: 1 避免多轮对话消耗过多 context
|
|
852
|
+
const { generateText } = require('ai')
|
|
853
|
+
|
|
854
|
+
const { text } = await generateText({
|
|
855
|
+
model: aiClient,
|
|
856
|
+
system: '你是一个记忆提取专家,直接返回 JSON,不要有其他内容。',
|
|
857
|
+
prompt,
|
|
858
|
+
maxTokens: 500,
|
|
859
|
+
temperature: 0.3,
|
|
860
|
+
maxSteps: 1
|
|
861
|
+
})
|
|
862
|
+
|
|
863
|
+
// 解析 JSON 响应
|
|
864
|
+
if (text) {
|
|
865
|
+
const cleanedText = text.trim()
|
|
866
|
+
|
|
867
|
+
// 移除 markdown 代码块包裹
|
|
868
|
+
let jsonText = cleanedText
|
|
869
|
+
if (jsonText.startsWith('```')) {
|
|
870
|
+
jsonText = jsonText.replace(/^```[\w]*\n?/, '').replace(/\n?```$/, '').trim()
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// 尝试提取并解析 JSON
|
|
874
|
+
const jsonStr = _extractJSON(jsonText)
|
|
875
|
+
if (jsonStr) {
|
|
876
|
+
try {
|
|
877
|
+
const result = JSON.parse(jsonStr)
|
|
878
|
+
return result
|
|
879
|
+
} catch (parseErr) {
|
|
880
|
+
// JSON 解析失败,尝试清理不可见字符后重试
|
|
881
|
+
const cleaned = jsonStr.replace(/[\u0000-\u001F\u007F-\u009F\u200B-\u200F\u2028\u2029]/g, '')
|
|
882
|
+
try {
|
|
883
|
+
const result = JSON.parse(cleaned)
|
|
884
|
+
return result
|
|
885
|
+
} catch (e) {
|
|
886
|
+
log.warn('Failed to parse extracted JSON:', jsonStr.substring(0, 200))
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
} catch (err) {
|
|
892
|
+
log.warn('Memory extraction agent failed:', err.message)
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
return null
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* 注册 memory-context 到系统提示
|
|
900
|
+
*/
|
|
901
|
+
_registerMemoryContext(framework) {
|
|
902
|
+
// 获取主 agent 并注册 memory-context 部分
|
|
903
|
+
const mainAgent = framework._mainAgent
|
|
904
|
+
if (mainAgent) {
|
|
905
|
+
mainAgent.registerPromptPart('memory-context', 350, () => {
|
|
906
|
+
return this._buildMemoryContext()
|
|
907
|
+
})
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// 也监听 framework:ready 事件,以防主 agent 还没创建
|
|
911
|
+
framework.on('framework:ready', () => {
|
|
912
|
+
const agent = framework._mainAgent
|
|
913
|
+
if (agent) {
|
|
914
|
+
agent.registerPromptPart('memory-context', 350, () => {
|
|
915
|
+
return this._buildMemoryContext()
|
|
916
|
+
})
|
|
917
|
+
}
|
|
918
|
+
})
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* 构建记忆上下文(注入到系统提示)
|
|
923
|
+
*/
|
|
924
|
+
_buildMemoryContext() {
|
|
925
|
+
try {
|
|
926
|
+
// 获取所有 user 类型记忆作为用户偏好
|
|
927
|
+
const userMemories = this._store.listByType(MEMORY_TYPES.USER, 10)
|
|
928
|
+
const projectMemories = this._store.listByType(MEMORY_TYPES.PROJECT, 5)
|
|
929
|
+
|
|
930
|
+
if (userMemories.length === 0 && projectMemories.length === 0) {
|
|
931
|
+
return null
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const parts = ['【记忆上下文】']
|
|
935
|
+
|
|
936
|
+
if (userMemories.length > 0) {
|
|
937
|
+
parts.push('【用户偏好】')
|
|
938
|
+
for (const m of userMemories) {
|
|
939
|
+
parts.push(`- ${m.name}: ${m.body.substring(0, 150)}${m.body.length > 150 ? '...' : ''}`)
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (projectMemories.length > 0) {
|
|
944
|
+
parts.push('【项目上下文】')
|
|
945
|
+
for (const m of projectMemories) {
|
|
946
|
+
const projectTag = m.project ? `[${m.project}] ` : ''
|
|
947
|
+
parts.push(`- ${projectTag}${m.name}: ${m.body.substring(0, 100)}${m.body.length > 100 ? '...' : ''}`)
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
return parts.join('\n')
|
|
952
|
+
} catch (err) {
|
|
953
|
+
log.warn('Failed to build memory context:', err.message)
|
|
954
|
+
return null
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* 获取存储实例(供其他插件使用)
|
|
960
|
+
*/
|
|
961
|
+
getStore() {
|
|
962
|
+
return this._store
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* 搜索记忆(公开 API)
|
|
967
|
+
*/
|
|
968
|
+
searchMemories(query, options = {}) {
|
|
969
|
+
return this._store.search(query, options)
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
reload(framework) {
|
|
973
|
+
this._framework = framework
|
|
974
|
+
// 重新初始化存储
|
|
975
|
+
this._store = new MemoryStore(this.config.memoryDir)
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
uninstall(framework) {
|
|
979
|
+
this._store = null
|
|
980
|
+
this._framework = null
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
module.exports = MemoryPlugin
|