foliko 1.1.4 → 1.1.6
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/agents/code-assistant.json +4 -1
- package/.agent/agents/file-assistant.json +4 -1
- package/.agent/agents/orchestrator-demo.md +53 -0
- package/.agent/agents/orchestrator.json +7 -0
- package/.agent/data/plugins-state.json +4 -0
- package/.agent/mcp_config.json +6 -0
- package/.agent/memory/feedback/mnrsiuoc-e1ru74.md +9 -0
- package/.agent/memory/feedback/mnrt2mmz-98az6n.md +9 -0
- package/.agent/memory/feedback/mnrtqrhm-kxsicz.md +9 -0
- package/.agent/memory/feedback/mnrts8vg-i0ngzp.md +15 -0
- package/.agent/memory/feedback/mnrtt7jt-c0trb2.md +9 -0
- package/.agent/memory/feedback/mnruc2f0-5s52la.md +16 -0
- package/.agent/memory/feedback/mnrumbmx-63sa0v.md +9 -0
- package/.agent/memory/project/mnrp7p5n-8enm2a.md +31 -0
- package/.agent/memory/project/mnrp9ifb-yynks0.md +40 -0
- package/.agent/memory/project/mnrpb3b8-f617s4.md +25 -0
- package/.agent/memory/project/mnrrmqgg-focprv.md +9 -0
- package/.agent/memory/project/mnrtykbh-6atsor.md +9 -0
- package/.agent/memory/project/mnru9jiu-kgau16.md +35 -0
- package/.agent/memory/reference/mnrnvpwo-rcqv9m.md +52 -0
- package/.agent/memory/reference/mnrovxvz-zy9xqm.md +25 -0
- package/.agent/memory/reference/mnroxabj-1b3930.md +68 -0
- package/.agent/memory/reference/mnrpjtlp-mnb9od.md +35 -0
- package/.agent/memory/reference/mnrps1x3-6b8xfm.md +28 -0
- package/.agent/memory/reference/mnrpt9ov-15er5w.md +22 -0
- package/.agent/memory/reference/mnrq82dn-y9tv9e.md +50 -0
- package/.agent/memory/reference/mnrqnr5v-v75drf.md +34 -0
- package/.agent/memory/reference/mnrrfzys-urudaf.md +31 -0
- package/.agent/memory/reference/mnrrocha-t0027n.md +21 -0
- package/.agent/memory/reference/mnrukklc-bxndsb.md +35 -0
- package/.agent/memory/user/mnrt39t8-8eosy0.md +9 -0
- package/.agent/plugins/marknative/fonts.zip +0 -0
- package/.agent/sessions/cli_default.json +4493 -1183
- package/.agent/test-agent.js +35 -0
- package/cli/src/commands/chat.js +69 -1
- package/cli/src/index.js +11 -2
- package/foliko-cloud-rising.png +0 -0
- package/foliko-dawn-of-ai.png +0 -0
- package/foliko-mindful-observation.png +0 -0
- package/foliko-stellar-dreams.png +0 -0
- package/foliko-zen-jing.png +0 -0
- package/foliko-zen-kong.png +0 -0
- package/foliko-zen-wu.png +0 -0
- package/package.json +2 -2
- package/plugins/coordinator-plugin.js +282 -0
- package/plugins/default-plugins.js +1 -1
- package/plugins/extension-executor-plugin.js +27 -1
- package/plugins/memory-plugin.js +228 -57
- package/src/core/agent-chat.js +206 -115
- package/src/core/agent.js +258 -71
- package/src/core/coordinator-manager.js +341 -0
- package/src/core/framework.js +50 -0
- package/src/core/plugin-base.js +30 -17
- package/src/core/sub-agent-config.js +8 -1
- package/src/core/worker-agent.js +176 -0
- package/src/executors/mcp-executor.js +4 -4
- package/zen_karesansui.png +0 -0
- package/.agent/plugins/marknative/update-readme.js +0 -134
- package/output/emoji-segoe-test-v2.png +0 -0
- package/output/emoji-segoe-test.png +0 -0
- package/output/emoji-test.png +0 -0
- package/output/emoji-windows-test.png +0 -0
- package/output/foliko-emoji-poster.png +0 -0
- package/output/foliko-muji-poster-final.png +0 -0
- package/output/foliko-muji-poster-v2.png +0 -0
- package/output/foliko-muji-poster.png +0 -0
- package/output/foliko-share.png +0 -0
- package/output/progress-circle-test.png +0 -0
- package/output/vb-agent-poster.png +0 -0
package/plugins/memory-plugin.js
CHANGED
|
@@ -10,7 +10,7 @@ const { z } = require('zod')
|
|
|
10
10
|
const fs = require('fs')
|
|
11
11
|
const path = require('path')
|
|
12
12
|
const { EventEmitter } = require('events')
|
|
13
|
-
|
|
13
|
+
const {cleanResponse} =require('../src/utils')
|
|
14
14
|
/**
|
|
15
15
|
* 记忆类型常量
|
|
16
16
|
*/
|
|
@@ -147,44 +147,60 @@ function _extractJSON(text) {
|
|
|
147
147
|
|
|
148
148
|
// 去掉首尾空白
|
|
149
149
|
text = text.trim()
|
|
150
|
-
if (!text.startsWith('{')) return null
|
|
151
150
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
// 方法1:如果直接以 { 开头,尝试直接解析
|
|
152
|
+
if (text.startsWith('{')) {
|
|
153
|
+
// 查找配对的括号位置
|
|
154
|
+
let braceCount = 0
|
|
155
|
+
let inString = false
|
|
156
|
+
let escaped = false
|
|
156
157
|
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
for (let i = 0; i < text.length; i++) {
|
|
159
|
+
const char = text[i]
|
|
159
160
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
if (escaped) {
|
|
162
|
+
escaped = false
|
|
163
|
+
continue
|
|
164
|
+
}
|
|
164
165
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
if (char === '\\' && inString) {
|
|
167
|
+
escaped = true
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
169
170
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
if (char === '"') {
|
|
172
|
+
inString = !inString
|
|
173
|
+
continue
|
|
174
|
+
}
|
|
174
175
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
176
|
+
if (!inString) {
|
|
177
|
+
if (char === '{') {
|
|
178
|
+
braceCount++
|
|
179
|
+
} else if (char === '}') {
|
|
180
|
+
braceCount--
|
|
181
|
+
if (braceCount === 0) {
|
|
182
|
+
return text.substring(0, i + 1)
|
|
183
|
+
}
|
|
183
184
|
}
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
187
|
}
|
|
187
188
|
|
|
189
|
+
// 方法2:从 thinking 文本中查找 JSON(处理 AI 输出 thinking 的情况)
|
|
190
|
+
// 查找最后一个 { 到最后一个 } 之间的内容
|
|
191
|
+
const firstBrace = text.indexOf('{')
|
|
192
|
+
const lastBrace = text.lastIndexOf('}')
|
|
193
|
+
|
|
194
|
+
if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
|
|
195
|
+
const potential = text.substring(firstBrace, lastBrace + 1)
|
|
196
|
+
try {
|
|
197
|
+
JSON.parse(potential) // 验证是否是有效 JSON
|
|
198
|
+
return potential
|
|
199
|
+
} catch (e) {
|
|
200
|
+
// 不是有效 JSON,继续尝试其他方法
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
188
204
|
return null
|
|
189
205
|
}
|
|
190
206
|
|
|
@@ -541,6 +557,7 @@ class MemoryPlugin extends Plugin {
|
|
|
541
557
|
this._framework = null
|
|
542
558
|
this._store = null
|
|
543
559
|
this._memoryAgent = null // 用于记忆提取的子 agent
|
|
560
|
+
this._isExtracting = false // 防止递归调用的标志
|
|
544
561
|
}
|
|
545
562
|
|
|
546
563
|
install(framework) {
|
|
@@ -552,6 +569,15 @@ class MemoryPlugin extends Plugin {
|
|
|
552
569
|
// 初始化存储
|
|
553
570
|
this._store = new MemoryStore(this.config.memoryDir)
|
|
554
571
|
|
|
572
|
+
// 创建复用的记忆提取子 Agent
|
|
573
|
+
this._memoryAgent = framework.createSubAgent({
|
|
574
|
+
name: 'memory-extractor',
|
|
575
|
+
role: '记忆提取专家',
|
|
576
|
+
description: '专门负责从对话和错误中提取记忆的子 Agent',
|
|
577
|
+
tools: {}, // 不需要额外工具,隔离主 Agent 工具
|
|
578
|
+
parentTools: [] // 不继承任何工具,纯粹做提取
|
|
579
|
+
})
|
|
580
|
+
|
|
555
581
|
// 注册工具
|
|
556
582
|
this._registerTools(framework)
|
|
557
583
|
|
|
@@ -787,12 +813,156 @@ class MemoryPlugin extends Plugin {
|
|
|
787
813
|
_setupAutoExtract(framework) {
|
|
788
814
|
// 监听 agent 消息完成事件,在对话轮次结束时提取记忆
|
|
789
815
|
framework.on('agent:message', async ({ userMessage, assistantResponse }) => {
|
|
816
|
+
// 防止递归调用:如果正在提取中,跳过此次事件
|
|
817
|
+
if (this._isExtracting) {
|
|
818
|
+
log.debug('Skipping agent:message event during memory extraction')
|
|
819
|
+
return
|
|
820
|
+
}
|
|
821
|
+
|
|
790
822
|
try {
|
|
791
823
|
await this._extractMemoryFromConversation(userMessage, assistantResponse)
|
|
792
824
|
} catch (err) {
|
|
793
825
|
log.warn('Auto memory extraction failed:', err.message)
|
|
794
826
|
}
|
|
795
827
|
})
|
|
828
|
+
|
|
829
|
+
// 监听所有工具调用错误(包括 ext_call, MCP, 原生工具等)
|
|
830
|
+
framework.on('tool:error', async (data) => {
|
|
831
|
+
try {
|
|
832
|
+
await this._extractErrorLesson({
|
|
833
|
+
name: data.name || data.tool || 'unknown-tool',
|
|
834
|
+
args: data.args || {},
|
|
835
|
+
error: data.error || data.message || String(data),
|
|
836
|
+
source: data.source || 'tool'
|
|
837
|
+
})
|
|
838
|
+
} catch (err) {
|
|
839
|
+
log.warn('Auto tool error lesson extraction failed:', err.message)
|
|
840
|
+
}
|
|
841
|
+
})
|
|
842
|
+
|
|
843
|
+
// 监听 tool-call 别名(AI SDK 6.x 格式)
|
|
844
|
+
framework.on('tool-error', async (data) => {
|
|
845
|
+
try {
|
|
846
|
+
await this._extractErrorLesson({
|
|
847
|
+
name: data.name || data.tool || 'unknown-tool',
|
|
848
|
+
args: data.args || {},
|
|
849
|
+
error: data.error || data.message || String(data),
|
|
850
|
+
source: data.source || 'tool'
|
|
851
|
+
})
|
|
852
|
+
} catch (err) {
|
|
853
|
+
log.warn('Auto tool-error lesson extraction failed:', err.message)
|
|
854
|
+
}
|
|
855
|
+
})
|
|
856
|
+
|
|
857
|
+
// 监听工具重试事件(重试可能意味着配置或参数问题)
|
|
858
|
+
framework.on('tool:retry', async (data) => {
|
|
859
|
+
try {
|
|
860
|
+
const { name, args, error, attempt } = data
|
|
861
|
+
await this._extractErrorLesson({
|
|
862
|
+
name: name || 'unknown-tool',
|
|
863
|
+
args: args || {},
|
|
864
|
+
error: `Retry attempt ${attempt} failed: ${error || data.message || 'unknown'}`,
|
|
865
|
+
source: 'tool-retry'
|
|
866
|
+
})
|
|
867
|
+
} catch (err) {
|
|
868
|
+
log.warn('Auto tool retry lesson extraction failed:', err.message)
|
|
869
|
+
}
|
|
870
|
+
})
|
|
871
|
+
|
|
872
|
+
// 监听 agent 对话过程中的错误
|
|
873
|
+
framework.on('error', async (data) => {
|
|
874
|
+
try {
|
|
875
|
+
const errorObj = typeof data === 'object' ? data : { error: String(data) }
|
|
876
|
+
await this._extractErrorLesson({
|
|
877
|
+
name: errorObj.context || 'agent-error',
|
|
878
|
+
args: errorObj.data || {},
|
|
879
|
+
error: errorObj.error || errorObj.message || String(data),
|
|
880
|
+
source: 'agent'
|
|
881
|
+
})
|
|
882
|
+
} catch (err) {
|
|
883
|
+
log.warn('Auto agent error lesson extraction failed:', err.message)
|
|
884
|
+
}
|
|
885
|
+
})
|
|
886
|
+
|
|
887
|
+
// 监听 agent status 错误
|
|
888
|
+
framework.on('status', async (data) => {
|
|
889
|
+
if (data.status === 'error' && data.error) {
|
|
890
|
+
try {
|
|
891
|
+
await this._extractErrorLesson({
|
|
892
|
+
name: 'agent-status-error',
|
|
893
|
+
args: {},
|
|
894
|
+
error: data.error,
|
|
895
|
+
source: 'agent-status'
|
|
896
|
+
})
|
|
897
|
+
} catch (err) {
|
|
898
|
+
log.warn('Auto status error lesson extraction failed:', err.message)
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
})
|
|
902
|
+
|
|
903
|
+
// 监听会话上下文错误
|
|
904
|
+
framework.on('session:context-destroyed', async (data) => {
|
|
905
|
+
// 上下文销毁通常是正常的,但如果有错误信息则记录
|
|
906
|
+
if (data && data.error) {
|
|
907
|
+
try {
|
|
908
|
+
await this._extractErrorLesson({
|
|
909
|
+
name: 'session-context-error',
|
|
910
|
+
args: { sessionId: data.sessionId },
|
|
911
|
+
error: data.error,
|
|
912
|
+
source: 'session'
|
|
913
|
+
})
|
|
914
|
+
} catch (err) {
|
|
915
|
+
log.warn('Auto session error lesson extraction failed:', err.message)
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
})
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* 从错误中提取经验教训并保存
|
|
923
|
+
*/
|
|
924
|
+
async _extractErrorLesson(errorData) {
|
|
925
|
+
const { name, args, error, source } = errorData
|
|
926
|
+
|
|
927
|
+
// 构建提取提示 - 强调"如何下次避免"
|
|
928
|
+
const extractPrompt = `你是一个经验总结专家。请分析以下错误,总结如何在未来避免类似错误。
|
|
929
|
+
|
|
930
|
+
错误信息:
|
|
931
|
+
- 工具名称: ${name}
|
|
932
|
+
- 调用参数: ${JSON.stringify(args)}
|
|
933
|
+
- 错误原因: ${error}
|
|
934
|
+
- 来源: ${source || 'unknown'}
|
|
935
|
+
|
|
936
|
+
请按以下格式返回(仅返回 JSON,不要有其他内容):
|
|
937
|
+
{
|
|
938
|
+
"extract": true/false,
|
|
939
|
+
"name": "【避免指南】具体说明避免什么(如:避免不传 plugin 参数调用 ext_call)",
|
|
940
|
+
"tags": ["error", "avoid", "${name}"],
|
|
941
|
+
"content": "以【下次这样做】为开头,说明正确的做法。例如:\n【下次这样做】调用 ext_call 时必须传入 plugin、tool、args 三个参数。错误原因:缺少 plugin 参数导致扩展插件无法定位。"
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
如果错误是无关紧要的(如用户主动取消、超时重试等),返回:
|
|
945
|
+
{"extract": false}`
|
|
946
|
+
|
|
947
|
+
try {
|
|
948
|
+
const result = await this._runMemoryExtractionAgent(extractPrompt)
|
|
949
|
+
|
|
950
|
+
if (result && result.extract === true) {
|
|
951
|
+
try {
|
|
952
|
+
const saved = this._store.add({
|
|
953
|
+
name: result.name,
|
|
954
|
+
type: result.type || MEMORY_TYPES.FEEDBACK,
|
|
955
|
+
project: null,
|
|
956
|
+
tags: result.tags || ['error', 'avoid'],
|
|
957
|
+
body: result.content
|
|
958
|
+
})
|
|
959
|
+
} catch (saveErr) {
|
|
960
|
+
log.warn('[Memory] Failed to save error lesson:', saveErr.message)
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
} catch (err) {
|
|
964
|
+
log.warn('[Memory] Error lesson extraction failed:', err.message)
|
|
965
|
+
}
|
|
796
966
|
}
|
|
797
967
|
|
|
798
968
|
/**
|
|
@@ -851,35 +1021,24 @@ ${typeof assistantResponse === 'string' ? assistantResponse : JSON.stringify(ass
|
|
|
851
1021
|
* 使用独立的轻量子 agent 处理记忆提取,不占用主 agent 资源
|
|
852
1022
|
*/
|
|
853
1023
|
async _runMemoryExtractionAgent(prompt) {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
log.warn('Memory extraction: AI plugin not found')
|
|
859
|
-
return null
|
|
860
|
-
}
|
|
1024
|
+
if (!this._memoryAgent) {
|
|
1025
|
+
log.warn('Memory extraction: memory sub-agent not found')
|
|
1026
|
+
return null
|
|
1027
|
+
}
|
|
861
1028
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
log.warn('Memory extraction: AI client not found')
|
|
865
|
-
return null
|
|
866
|
-
}
|
|
1029
|
+
// 设置提取标志,防止递归调用
|
|
1030
|
+
this._isExtracting = true
|
|
867
1031
|
|
|
868
|
-
|
|
869
|
-
const
|
|
1032
|
+
try {
|
|
1033
|
+
const fullPrompt = `你是一个记忆提取专家。直接返回 JSON 格式的答案,不要输出任何思考过程、解释或其他内容。格式:{"extract":true/false,"name":"...","tags":[...],"content":"..."}
|
|
870
1034
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
prompt,
|
|
875
|
-
maxTokens: 500,
|
|
876
|
-
temperature: 0.3,
|
|
877
|
-
maxSteps: 1
|
|
878
|
-
})
|
|
1035
|
+
${prompt}`
|
|
1036
|
+
|
|
1037
|
+
const result = await this._memoryAgent.chat(fullPrompt, { maxSteps: 1 })
|
|
879
1038
|
|
|
880
1039
|
// 解析 JSON 响应
|
|
881
|
-
if (
|
|
882
|
-
const cleanedText =
|
|
1040
|
+
if (result?.message) {
|
|
1041
|
+
const cleanedText = cleanResponse(result.message.trim())
|
|
883
1042
|
|
|
884
1043
|
// 移除 markdown 代码块包裹
|
|
885
1044
|
let jsonText = cleanedText
|
|
@@ -891,14 +1050,14 @@ ${typeof assistantResponse === 'string' ? assistantResponse : JSON.stringify(ass
|
|
|
891
1050
|
const jsonStr = _extractJSON(jsonText)
|
|
892
1051
|
if (jsonStr) {
|
|
893
1052
|
try {
|
|
894
|
-
const
|
|
895
|
-
return
|
|
1053
|
+
const parsed = JSON.parse(jsonStr)
|
|
1054
|
+
return parsed
|
|
896
1055
|
} catch (parseErr) {
|
|
897
1056
|
// JSON 解析失败,尝试清理不可见字符后重试
|
|
898
1057
|
const cleaned = jsonStr.replace(/[\u0000-\u001F\u007F-\u009F\u200B-\u200F\u2028\u2029]/g, '')
|
|
899
1058
|
try {
|
|
900
|
-
const
|
|
901
|
-
return
|
|
1059
|
+
const parsed = JSON.parse(cleaned)
|
|
1060
|
+
return parsed
|
|
902
1061
|
} catch (e) {
|
|
903
1062
|
log.warn('Failed to parse extracted JSON:', jsonStr.substring(0, 200))
|
|
904
1063
|
}
|
|
@@ -907,6 +1066,9 @@ ${typeof assistantResponse === 'string' ? assistantResponse : JSON.stringify(ass
|
|
|
907
1066
|
}
|
|
908
1067
|
} catch (err) {
|
|
909
1068
|
log.warn('Memory extraction agent failed:', err.message)
|
|
1069
|
+
} finally {
|
|
1070
|
+
// 必须清除提取标志
|
|
1071
|
+
this._isExtracting = false
|
|
910
1072
|
}
|
|
911
1073
|
|
|
912
1074
|
return null
|
|
@@ -955,13 +1117,22 @@ ${typeof assistantResponse === 'string' ? assistantResponse : JSON.stringify(ass
|
|
|
955
1117
|
try {
|
|
956
1118
|
const userMemories = this._store.listByType(MEMORY_TYPES.USER, 10)
|
|
957
1119
|
const projectMemories = this._store.listByType(MEMORY_TYPES.PROJECT, 5)
|
|
1120
|
+
const feedbackMemories = this._store.search('', { type: MEMORY_TYPES.FEEDBACK, tags: ['avoid'], limit: 10 })
|
|
958
1121
|
|
|
959
|
-
if (userMemories.length === 0 && projectMemories.length === 0) {
|
|
1122
|
+
if (userMemories.length === 0 && projectMemories.length === 0 && feedbackMemories.length === 0) {
|
|
960
1123
|
return null
|
|
961
1124
|
}
|
|
962
1125
|
|
|
963
1126
|
const parts = ['【记忆上下文】']
|
|
964
1127
|
|
|
1128
|
+
// 错误避免指南(优先显示,放在最前面)
|
|
1129
|
+
if (feedbackMemories.length > 0) {
|
|
1130
|
+
parts.push('【错误避免指南 - 必须遵守】')
|
|
1131
|
+
for (const m of feedbackMemories) {
|
|
1132
|
+
parts.push(`- ${m.body}`)
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
965
1136
|
if (userMemories.length > 0) {
|
|
966
1137
|
parts.push('【用户偏好】')
|
|
967
1138
|
for (const m of userMemories) {
|