foliko 1.1.35 → 1.1.38
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/scheduler/tasks.json +1 -86
- package/.agent/memory/feedback/mocnx1wx-qtxxlh.md +9 -0
- package/.agent/sessions/cli_default.json +21 -280
- package/.claude/settings.local.json +4 -1
- package/cli/src/ui/chat-ui.js +29 -1
- package/package.json +2 -1
- package/plugins/feishu-plugin.js +36 -3
- package/plugins/scheduler-plugin.js +17 -17
- package/plugins/telegram-plugin.js +43 -7
- package/plugins/weixin-plugin.js +75 -0
- package/src/core/agent-chat.js +11 -6
- package/src/core/agent.js +5 -4
- package/src/core/context-compressor.js +6 -1
- package/src/core/context-manager.js +4 -93
- package/src/core/framework.js +14 -9
- package/src/core/session-context.js +5 -0
- package/src/core/session-storage.js +40 -19
|
@@ -1,86 +1 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"id": "task_1776247937913_s7cji07zc",
|
|
4
|
-
"name": "比特币价格检查",
|
|
5
|
-
"type": "cron",
|
|
6
|
-
"message": "触发比特币价格监控目标",
|
|
7
|
-
"enabled": false,
|
|
8
|
-
"createdAt": "2026-04-15T10:12:17.913Z",
|
|
9
|
-
"lastRun": "2026-04-15T10:16:00.022Z",
|
|
10
|
-
"runCount": 2,
|
|
11
|
-
"cronExpression": "*/2 * * * *",
|
|
12
|
-
"sessionId": "cli_default",
|
|
13
|
-
"llm": false,
|
|
14
|
-
"notify": true
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"id": "task_1776248745245_sas6qyl74",
|
|
18
|
-
"name": "BTC价格检查V2",
|
|
19
|
-
"type": "cron",
|
|
20
|
-
"message": "触发比特币价格监控V2目标",
|
|
21
|
-
"enabled": false,
|
|
22
|
-
"createdAt": "2026-04-15T10:25:45.245Z",
|
|
23
|
-
"lastRun": "2026-04-15T10:28:00.028Z",
|
|
24
|
-
"runCount": 2,
|
|
25
|
-
"cronExpression": "*/2 * * * *",
|
|
26
|
-
"sessionId": "cli_default",
|
|
27
|
-
"llm": false,
|
|
28
|
-
"notify": true
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"id": "task_1776248889631_e4ksf553t",
|
|
32
|
-
"name": "BTC价格测试",
|
|
33
|
-
"type": "cron",
|
|
34
|
-
"message": "触发比特币价格监控测试",
|
|
35
|
-
"enabled": false,
|
|
36
|
-
"createdAt": "2026-04-15T10:28:09.631Z",
|
|
37
|
-
"lastRun": null,
|
|
38
|
-
"runCount": 0,
|
|
39
|
-
"cronExpression": "* * * * *",
|
|
40
|
-
"sessionId": "cli_default",
|
|
41
|
-
"llm": false,
|
|
42
|
-
"notify": true
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"id": "task_1776248948304_3lvaf4gvu",
|
|
46
|
-
"name": "BTC价格监控V3",
|
|
47
|
-
"type": "cron",
|
|
48
|
-
"message": "触发BTC价格监控V3",
|
|
49
|
-
"enabled": false,
|
|
50
|
-
"createdAt": "2026-04-15T10:29:08.304Z",
|
|
51
|
-
"lastRun": "2026-04-15T10:32:00.025Z",
|
|
52
|
-
"runCount": 3,
|
|
53
|
-
"cronExpression": "* * * * *",
|
|
54
|
-
"sessionId": "cli_default",
|
|
55
|
-
"llm": false,
|
|
56
|
-
"notify": true
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
"id": "task_1776249148016_pe51kdbd7",
|
|
60
|
-
"name": "BTC价格监控V4",
|
|
61
|
-
"type": "cron",
|
|
62
|
-
"message": "触发BTC价格监控V4",
|
|
63
|
-
"enabled": false,
|
|
64
|
-
"createdAt": "2026-04-15T10:32:28.016Z",
|
|
65
|
-
"lastRun": "2026-04-15T10:36:00.019Z",
|
|
66
|
-
"runCount": 2,
|
|
67
|
-
"cronExpression": "*/2 * * * *",
|
|
68
|
-
"sessionId": "cli_default",
|
|
69
|
-
"llm": false,
|
|
70
|
-
"notify": true
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
"id": "task_1776249256126_dd3yp6bhd",
|
|
74
|
-
"name": "手动测试",
|
|
75
|
-
"type": "cron",
|
|
76
|
-
"message": "手动测试触发",
|
|
77
|
-
"enabled": false,
|
|
78
|
-
"createdAt": "2026-04-15T10:34:16.126Z",
|
|
79
|
-
"lastRun": "2026-04-15T10:37:00.021Z",
|
|
80
|
-
"runCount": 3,
|
|
81
|
-
"cronExpression": "*/1 * * * *",
|
|
82
|
-
"sessionId": "cli_default",
|
|
83
|
-
"llm": false,
|
|
84
|
-
"notify": true
|
|
85
|
-
}
|
|
86
|
-
]
|
|
1
|
+
[]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: "mocnx1wx-qtxxlh"
|
|
3
|
+
name: "用户对价格的反馈"
|
|
4
|
+
type: "feedback"
|
|
5
|
+
project: "null"
|
|
6
|
+
tags: [价格, 反馈, 定价]
|
|
7
|
+
created: "2026-04-24"
|
|
8
|
+
---
|
|
9
|
+
用户认为价格偏贵,表达了'貌似还是有点贵'的反馈。Why: 价格敏感度影响用户转化和留存,了解用户对价格的看法有助于优化定价策略。How to apply: 在后续与用户沟通时,可适当解释价值主张或提供优惠方案;在产品定价讨论中,可参考此反馈调整价格策略。
|
|
@@ -3,189 +3,23 @@
|
|
|
3
3
|
"messages": [
|
|
4
4
|
{
|
|
5
5
|
"role": "user",
|
|
6
|
-
"content": "
|
|
7
|
-
},
|
|
8
|
-
{
|
|
9
|
-
"role": "assistant",
|
|
10
|
-
"content": [
|
|
11
|
-
{
|
|
12
|
-
"type": "reasoning",
|
|
13
|
-
"text": "用户打了个招呼,我应该友好回应,并简要介绍一下我能帮他做什么。不需要调用工具。"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"type": "text",
|
|
17
|
-
"text": "你好!有什么可以帮你的?"
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"role": "user",
|
|
23
|
-
"content": "你是什么模型?"
|
|
6
|
+
"content": "30秒后提醒我吃饭了"
|
|
24
7
|
},
|
|
25
8
|
{
|
|
26
9
|
"role": "assistant",
|
|
27
10
|
"content": [
|
|
28
|
-
{
|
|
29
|
-
"type": "reasoning",
|
|
30
|
-
"text": "The user is asking what model I am. I don't have direct access to my model name in the system prompt, but I can try to find out by checking configuration files or environment variables. Let me look for any relevant information.\n\nActually, let me be honest - I don't have my model name explicitly stated in the system prompt. I should tell the user what I know based on the context I have. I can see this is a Foliko-based agent system. Let me check if there are any configuration files that might reveal the model name."
|
|
31
|
-
},
|
|
32
11
|
{
|
|
33
12
|
"type": "text",
|
|
34
|
-
"text": "
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"type": "tool-call",
|
|
38
|
-
"toolCallId": "call_00_pRQ6jjOBdfr0ewbkSPgxVNKB",
|
|
39
|
-
"toolName": "read_file",
|
|
40
|
-
"input": {
|
|
41
|
-
"path": "D:\\code\\vb-agent\\.agent"
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"type": "tool-call",
|
|
46
|
-
"toolCallId": "call_01_Z72S50DvSO1CL2Gxj0bvJBkE",
|
|
47
|
-
"toolName": "search_file",
|
|
48
|
-
"input": {
|
|
49
|
-
"pattern": "model",
|
|
50
|
-
"path": "D:\\code\\vb-agent",
|
|
51
|
-
"fileType": ".json",
|
|
52
|
-
"maxResults": 10
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
]
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"role": "tool",
|
|
59
|
-
"content": [
|
|
60
|
-
{
|
|
61
|
-
"type": "tool-result",
|
|
62
|
-
"toolCallId": "call_00_pRQ6jjOBdfr0ewbkSPgxVNKB",
|
|
63
|
-
"toolName": "read_file",
|
|
64
|
-
"output": {
|
|
65
|
-
"type": "text",
|
|
66
|
-
"value": "{\"success\":false,\"error\":\"EISDIR: illegal operation on a directory, read\"}"
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
"type": "tool-result",
|
|
71
|
-
"toolCallId": "call_01_Z72S50DvSO1CL2Gxj0bvJBkE",
|
|
72
|
-
"toolName": "search_file",
|
|
73
|
-
"output": {
|
|
74
|
-
"type": "text",
|
|
75
|
-
"value": "{\"success\":true,\"pattern\":\"model\",\"results\":[{\"file\":\".agent\\\\data\\\\plugins-state.json\",\"line\":138,\"content\":\" \\\"model\\\": \\\"inherit\\\",\",\"matches\":[{\"column\":11,\"length\":5,\"text\":\"model\"}]},{\"file\":\".agent\\\\data\\\\plugins-state.json\",\"line\":146,\"content\":\" \\\"model\\\": \\\"inherit\\\",\",\"matches\":[{\"column\":11,\"length\":5,\"text\":\"model\"}]},{\"file\":\".agent\\\\data\\\\plugins-state.json\",\"line\":154,\"content\":\" \\\"model\\\": \\\"inherit\\\",\",\"matches\":[{\"column\":11,\"length\":5,\"text\":\"model\"}]},{\"file\":\".agent\\\\data\\\\plugins-state.json\",\"line\":162,\"content\":\" \\\"model\\\": \\\"inherit\\\",\",\"matches\":[{\"column\":11,\"length\":5,\"text\":\"model\"}]},{\"file\":\".agent\\\\data\\\\plugins-state.json\",\"line\":170,\"content\":\" \\\"model\\\": \\\"inherit\\\",\",\"matches\":[{\"column\":11,\"length\":5,\"text\":\"model\"}]},{\"file\":\".agent\\\\data\\\\plugins-state.json\",\"line\":178,\"content\":\" \\\"model\\\": \\\"inherit\\\",\",\"matches\":[{\"column\":11,\"length\":5,\"text\":\"model\"}]},{\"file\":\".agent\\\\data\\\\plugins-state.json\",\"line\":186,\"content\":\" \\\"model\\\": \\\"inherit\\\",\",\"matches\":[{\"column\":11,\"length\":5,\"text\":\"model\"}]},{\"file\":\".agent\\\\data\\\\plugins-state.json\",\"line\":194,\"content\":\" \\\"model\\\": \\\"inherit\\\",\",\"matches\":[{\"column\":11,\"length\":5,\"text\":\"model\"}]},{\"file\":\"package-lock.json\",\"line\":20,\"content\":\" \\\"@modelcontextprotocol/sdk\\\": \\\"^1.27.1\\\",\",\"matches\":[{\"column\":10,\"length\":5,\"text\":\"model\"}]},{\"file\":\"package-lock.json\",\"line\":321,\"content\":\" \\\"node_modules/@modelcontextprotocol/sdk\\\": {\",\"matches\":[{\"column\":19,\"length\":5,\"text\":\"model\"}]}],\"total\":10,\"stats\":{\"filesWithMatches\":2,\"totalMatches\":10,\"searchPath\":\"D:\\\\code\\\\vb-agent\"}}"
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
]
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
"role": "assistant",
|
|
82
|
-
"content": [
|
|
83
|
-
{
|
|
84
|
-
"type": "reasoning",
|
|
85
|
-
"text": "Let me check the agent configuration files more carefully."
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"type": "tool-call",
|
|
89
|
-
"toolCallId": "call_00_1o9Wkj7lvt51H4VQ4p56lys7",
|
|
90
|
-
"toolName": "read_directory",
|
|
91
|
-
"input": {
|
|
92
|
-
"path": "D:\\code\\vb-agent\\.agent"
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
]
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
"role": "tool",
|
|
99
|
-
"content": [
|
|
100
|
-
{
|
|
101
|
-
"type": "tool-result",
|
|
102
|
-
"toolCallId": "call_00_1o9Wkj7lvt51H4VQ4p56lys7",
|
|
103
|
-
"toolName": "read_directory",
|
|
104
|
-
"output": {
|
|
105
|
-
"type": "text",
|
|
106
|
-
"value": "{\"success\":true,\"dirPath\":\"D:\\\\code\\\\vb-agent\\\\.agent\",\"items\":[{\"name\":\".shared\",\"path\":\".agent\\\\.shared\",\"type\":\"directory\",\"size\":null},{\"name\":\"agents\",\"path\":\".agent\\\\agents\",\"type\":\"directory\",\"size\":null},{\"name\":\"ARCHITECTURE.md\",\"path\":\".agent\\\\ARCHITECTURE.md\",\"type\":\"file\",\"size\":11566},{\"name\":\"data\",\"path\":\".agent\\\\data\",\"type\":\"directory\",\"size\":null},{\"name\":\"logs\",\"path\":\".agent\\\\logs\",\"type\":\"directory\",\"size\":null},{\"name\":\"mcp_config.json\",\"path\":\".agent\\\\mcp_config.json\",\"type\":\"file\",\"size\":684},{\"name\":\"memory\",\"path\":\".agent\\\\memory\",\"type\":\"directory\",\"size\":null},{\"name\":\"plugins\",\"path\":\".agent\\\\plugins\",\"type\":\"directory\",\"size\":null},{\"name\":\"plugins.json\",\"path\":\".agent\\\\plugins.json\",\"type\":\"file\",\"size\":88},{\"name\":\"python-venvs\",\"path\":\".agent\\\\python-venvs\",\"type\":\"directory\",\"size\":null},{\"name\":\"rules\",\"path\":\".agent\\\\rules\",\"type\":\"directory\",\"size\":null},{\"name\":\"scripts\",\"path\":\".agent\\\\scripts\",\"type\":\"directory\",\"size\":null},{\"name\":\"sessions\",\"path\":\".agent\\\\sessions\",\"type\":\"directory\",\"size\":null},{\"name\":\"skills\",\"path\":\".agent\\\\skills\",\"type\":\"directory\",\"size\":null},{\"name\":\"workflows\",\"path\":\".agent\\\\workflows\",\"type\":\"directory\",\"size\":null}],\"total\":15}"
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
]
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
"role": "assistant",
|
|
113
|
-
"content": [
|
|
114
|
-
{
|
|
115
|
-
"type": "reasoning",
|
|
116
|
-
"text": "Let me check the main config or package.json to see which model is being used."
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
"type": "tool-call",
|
|
120
|
-
"toolCallId": "call_00_6Z012Vqf0Eawctf7AoHVVdXR",
|
|
121
|
-
"toolName": "search_file",
|
|
122
|
-
"input": {
|
|
123
|
-
"pattern": "model|provider|llm",
|
|
124
|
-
"path": "D:\\code\\vb-agent",
|
|
125
|
-
"fileType": ".json",
|
|
126
|
-
"maxResults": 5,
|
|
127
|
-
"caseSensitive": false
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
"type": "tool-call",
|
|
132
|
-
"toolCallId": "call_01_kDaNLT9XHeEkuCMnPUBmgjcx",
|
|
133
|
-
"toolName": "read_file",
|
|
134
|
-
"input": {
|
|
135
|
-
"path": "D:\\code\\vb-agent\\package.json",
|
|
136
|
-
"lines": 30
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
]
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
"role": "tool",
|
|
143
|
-
"content": [
|
|
144
|
-
{
|
|
145
|
-
"type": "tool-result",
|
|
146
|
-
"toolCallId": "call_00_6Z012Vqf0Eawctf7AoHVVdXR",
|
|
147
|
-
"toolName": "search_file",
|
|
148
|
-
"output": {
|
|
149
|
-
"type": "text",
|
|
150
|
-
"value": "{\"success\":true,\"pattern\":\"model|provider|llm\",\"results\":[],\"total\":0,\"stats\":{\"filesWithMatches\":0,\"totalMatches\":0,\"searchPath\":\"D:\\\\code\\\\vb-agent\"}}"
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
"type": "tool-result",
|
|
155
|
-
"toolCallId": "call_01_kDaNLT9XHeEkuCMnPUBmgjcx",
|
|
156
|
-
"toolName": "read_file",
|
|
157
|
-
"output": {
|
|
158
|
-
"type": "text",
|
|
159
|
-
"value": "{\"success\":true,\"filePath\":\"D:\\\\code\\\\vb-agent\\\\package.json\",\"content\":\"{\\n \\\"name\\\": \\\"foliko\\\",\\n \\\"version\\\": \\\"1.1.34\\\",\\n \\\"description\\\": \\\"简约的插件化 Agent 框架\\\",\\n \\\"main\\\": \\\"src/index.js\\\",\\n \\\"type\\\": \\\"commonjs\\\",\\n \\\"bin\\\": {\\n \\\"foliko\\\": \\\"./cli/bin/foliko.js\\\"\\n },\\n \\\"scripts\\\": {\\n \\\"start\\\": \\\"node examples/basic.js\\\",\\n \\\"chat\\\": \\\"node cli/bin/foliko.js chat\\\",\\n \\\"dev\\\": \\\"node --watch examples/basic.js\\\",\\n \\\"lint\\\": \\\"echo \\\\\\\"No linter configured\\\\\\\"\\\",\\n \\\"format\\\": \\\"prettier --write \\\\\\\"**/*.{js,jsx,ts,tsx,json,yaml,yml,md,css,html}\\\\\\\"\\\",\\n \\\"format:check\\\": \\\"prettier --check \\\\\\\"**/*.{js,jsx,ts,tsx,json,yaml,yml,md,css,html}\\\\\\\"\\\",\\n \\\"prepare\\\": \\\"husky\\\"\\n },\\n \\\"lint-staged\\\": {\\n \\\"*.{js,jsx,ts,tsx}\\\": [\\n \\\"prettier --write\\\"\\n ],\\n \\\"*.{json,yaml,yml}\\\": [\\n \\\"prettier --write\\\"\\n ],\\n \\\"*.{md,mdx}\\\": [\\n \\\"prettier --write\\\"\\n ],\\n \\\"*.{css,scss,less}\\\": [\\n \\\"prettier --write\\\"\",\"size\":2140,\"lines\":null}"
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
]
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
"role": "assistant",
|
|
166
|
-
"content": [
|
|
167
|
-
{
|
|
168
|
-
"type": "reasoning",
|
|
169
|
-
"text": "Let me look at the ARCHITECTURE.md or the agent configuration to find the model name."
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
"type": "tool-call",
|
|
173
|
-
"toolCallId": "call_00_kC3mIrqFnpDMPc87Pbok5wvx",
|
|
174
|
-
"toolName": "read_file",
|
|
175
|
-
"input": {
|
|
176
|
-
"path": "D:\\code\\vb-agent\\.agent\\ARCHITECTURE.md",
|
|
177
|
-
"lines": 50
|
|
178
|
-
}
|
|
13
|
+
"text": "<think>\n用户想要设置一个30秒后的吃饭提醒。这是一个简单的定时任务,我可以使用 schedule_task 工具来设置。\n\n时间格式支持相对时间,30秒可以直接写 \"30 seconds\"。\n</think>\n\n\n"
|
|
179
14
|
},
|
|
180
15
|
{
|
|
181
16
|
"type": "tool-call",
|
|
182
|
-
"toolCallId": "
|
|
183
|
-
"toolName": "
|
|
17
|
+
"toolCallId": "call_function_dz1v0vse0j9h_1",
|
|
18
|
+
"toolName": "schedule_task",
|
|
184
19
|
"input": {
|
|
185
|
-
"
|
|
186
|
-
"
|
|
187
|
-
"
|
|
188
|
-
"caseSensitive": false
|
|
20
|
+
"name": "吃饭提醒",
|
|
21
|
+
"scheduleTime": "30 seconds",
|
|
22
|
+
"message": "吃饭时间到了!🍽️"
|
|
189
23
|
}
|
|
190
24
|
}
|
|
191
25
|
]
|
|
@@ -195,20 +29,11 @@
|
|
|
195
29
|
"content": [
|
|
196
30
|
{
|
|
197
31
|
"type": "tool-result",
|
|
198
|
-
"toolCallId": "
|
|
199
|
-
"toolName": "
|
|
200
|
-
"output": {
|
|
201
|
-
"type": "text",
|
|
202
|
-
"value": "{\"success\":true,\"filePath\":\"D:\\\\code\\\\vb-agent\\\\.agent\\\\ARCHITECTURE.md\",\"content\":\"# Antigravity Kit Architecture\\n\\n> Comprehensive AI Agent Capability Expansion Toolkit\\n\\n---\\n\\n## 📋 Overview\\n\\nAntigravity Kit is a modular system consisting of:\\n\\n- **20 Specialist Agents** - Role-based AI personas\\n- **36 Skills** - Domain-specific knowledge modules\\n- **11 Workflows** - Slash command procedures\\n\\n---\\n\\n## 🏗️ Directory Structure\\n\\n```plaintext\\n.agent/\\n├── ARCHITECTURE.md # This file\\n├── agents/ # 20 Specialist Agents\\n├── skills/ # 36 Skills\\n├── workflows/ # 11 Slash Commands\\n├── rules/ # Global Rules\\n└── scripts/ # Master Validation Scripts\\n```\\n\\n---\\n\\n## 🤖 Agents (20)\\n\\nSpecialist AI personas for different domains.\\n\\n| Agent | Focus | Skills Used |\\n| ------------------------ | -------------------------- | -------------------------------------------------------- |\\n| `orchestrator` | Multi-agent coordination | parallel-agents, behavioral-modes |\\n| `project-planner` | Discovery, task planning | brainstorming, plan-writing, architecture |\\n| `frontend-specialist` | Web UI/UX | frontend-design, react-best-practices, tailwind-patterns |\\n| `backend-specialist` | API, business logic | api-patterns, nodejs-best-practices, database-design |\\n| `database-architect` | Schema, SQL | database-design, prisma-expert |\\n| `mobile-developer` | iOS, Android, RN | mobile-design |\\n| `game-developer` | Game logic, mechanics | game-development |\\n| `devops-engineer` | CI/CD, Docker | deployment-procedures, docker-expert |\\n| `security-auditor` | Security compliance | vulnerability-scanner, red-team-tactics |\\n| `penetration-tester` | Offensive security | red-team-tactics |\\n| `test-engineer` | Testing strategies | testing-patterns, tdd-workflow, webapp-testing |\\n| `debugger` | Root cause analysis | systematic-debugging |\\n| `performance-optimizer` | Speed, Web Vitals | performance-profiling |\\n| `seo-specialist` | Ranking, visibility | seo-fundamentals, geo-fundamentals |\",\"size\":11566,\"lines\":null}"
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
"type": "tool-result",
|
|
207
|
-
"toolCallId": "call_01_xHeVX63CL4KMNBip1tlngsl4",
|
|
208
|
-
"toolName": "search_file",
|
|
32
|
+
"toolCallId": "call_function_dz1v0vse0j9h_1",
|
|
33
|
+
"toolName": "schedule_task",
|
|
209
34
|
"output": {
|
|
210
35
|
"type": "text",
|
|
211
|
-
"value": "{\"success\":true,\"
|
|
36
|
+
"value": "{\"success\":true,\"taskId\":\"task_1777342622294_wevunc6jz\",\"name\":\"吃饭提醒\",\"scheduleTime\":\"30 seconds\",\"executeAt\":\"2026-04-28T02:17:32.294Z\",\"message\":\"提醒已设置\",\"sessionId\":\"default\",\"llm\":false,\"notify\":true}"
|
|
212
37
|
}
|
|
213
38
|
}
|
|
214
39
|
]
|
|
@@ -217,125 +42,41 @@
|
|
|
217
42
|
"role": "assistant",
|
|
218
43
|
"content": [
|
|
219
44
|
{
|
|
220
|
-
"type": "
|
|
221
|
-
"text": "
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
"type": "tool-call",
|
|
225
|
-
"toolCallId": "call_00_FjYABVn3EfWSc29MGJ5kJ3FP",
|
|
226
|
-
"toolName": "search_file",
|
|
227
|
-
"input": {
|
|
228
|
-
"pattern": "\\.env",
|
|
229
|
-
"path": "D:\\code\\vb-agent",
|
|
230
|
-
"maxResults": 5,
|
|
231
|
-
"caseSensitive": false
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
]
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
"role": "tool",
|
|
238
|
-
"content": [
|
|
239
|
-
{
|
|
240
|
-
"type": "tool-result",
|
|
241
|
-
"toolCallId": "call_00_FjYABVn3EfWSc29MGJ5kJ3FP",
|
|
242
|
-
"toolName": "search_file",
|
|
243
|
-
"output": {
|
|
244
|
-
"type": "text",
|
|
245
|
-
"value": "{\"success\":true,\"pattern\":\"\\\\.env\",\"results\":[{\"file\":\".agent\\\\rules\\\\security-rule.md\",\"line\":32,\"content\":\" pattern: \\\"^.*/\\\\\\\\.env\\\"\",\"matches\":[{\"column\":19,\"length\":5,\"text\":\"\\\\.env\"}]}],\"total\":1,\"stats\":{\"filesWithMatches\":1,\"totalMatches\":1,\"searchPath\":\"D:\\\\code\\\\vb-agent\"}}"
|
|
246
|
-
}
|
|
45
|
+
"type": "text",
|
|
46
|
+
"text": "<think>\n定时任务已设置成功,30秒后系统会发送吃饭提醒。\n</think>\n\n已设置30秒后提醒你吃饭!🍽️"
|
|
247
47
|
}
|
|
248
48
|
]
|
|
249
49
|
},
|
|
250
50
|
{
|
|
251
|
-
"role": "
|
|
252
|
-
"content":
|
|
253
|
-
{
|
|
254
|
-
"type": "reasoning",
|
|
255
|
-
"text": "I couldn't find the specific model name in any configuration files. The system prompt doesn't disclose the model name. Let me be honest with the user about this. I'm running within the Foliko Agent framework, and the underlying LLM model isn't directly exposed to me."
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
"type": "text",
|
|
259
|
-
"text": "我无法直接获取底层模型的具体名称——系统提示中没有明确标注,配置文件中也没有直接暴露这个信息。\n\n我运行在 **Foliko Agent 框架**(v1.1.34)之上,这是一个插件化的 Agent 框架,底层可以接入多种 LLM 模型(如 DeepSeek、Claude、GPT、Qwen、MiniMax 等),具体使用的是哪个模型取决于当前配置的 provider 设置。\n\n如果你想确认具体模型,可以查看启动命令或环境变量中的模型配置。有什么其他问题我可以帮你的吗?"
|
|
260
|
-
}
|
|
261
|
-
]
|
|
51
|
+
"role": "user",
|
|
52
|
+
"content": "你好啊"
|
|
262
53
|
},
|
|
263
54
|
{
|
|
264
55
|
"role": "user",
|
|
265
|
-
"content": "
|
|
56
|
+
"content": "你好"
|
|
266
57
|
},
|
|
267
58
|
{
|
|
268
59
|
"role": "assistant",
|
|
269
60
|
"content": [
|
|
270
61
|
{
|
|
271
62
|
"type": "reasoning",
|
|
272
|
-
"text": "The user is
|
|
63
|
+
"text": "The user is just greeting me. I should respond naturally. Let me also check if the meal reminder has been triggered."
|
|
273
64
|
},
|
|
274
65
|
{
|
|
275
66
|
"type": "text",
|
|
276
|
-
"text": "
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
"type": "tool-call",
|
|
280
|
-
"toolCallId": "call_00_wizIezCgqlmapVoUVUfJsjbC",
|
|
281
|
-
"toolName": "subagent_call",
|
|
282
|
-
"input": {
|
|
283
|
-
"agentName": "frontend-dev",
|
|
284
|
-
"task": "简单演示:用一句话概括你的能力"
|
|
285
|
-
}
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
"type": "tool-call",
|
|
289
|
-
"toolCallId": "call_01_KMWm0UOAv4UuiDn7uecqAQrs",
|
|
290
|
-
"toolName": "subagent_call",
|
|
291
|
-
"input": {
|
|
292
|
-
"agentName": "backend-dev",
|
|
293
|
-
"task": "简单演示:用一句话概括你的能力"
|
|
294
|
-
}
|
|
67
|
+
"text": "你好!😊 有什么需要帮忙的吗?"
|
|
295
68
|
}
|
|
296
69
|
]
|
|
297
70
|
},
|
|
298
71
|
{
|
|
299
|
-
"role": "
|
|
300
|
-
"content":
|
|
301
|
-
{
|
|
302
|
-
"type": "tool-result",
|
|
303
|
-
"toolCallId": "call_00_wizIezCgqlmapVoUVUfJsjbC",
|
|
304
|
-
"toolName": "subagent_call",
|
|
305
|
-
"output": {
|
|
306
|
-
"type": "text",
|
|
307
|
-
"value": "{\"success\":true,\"agent\":\"frontend-dev\",\"result\":\"作为前端开发专家,我专注于用 **React/Vue/TypeScript** 构建高性能组件、实现响应式界面、优化加载性能,并保证代码的类型安全与可维护性。\"}"
|
|
308
|
-
}
|
|
309
|
-
},
|
|
310
|
-
{
|
|
311
|
-
"type": "tool-result",
|
|
312
|
-
"toolCallId": "call_01_KMWm0UOAv4UuiDn7uecqAQrs",
|
|
313
|
-
"toolName": "subagent_call",
|
|
314
|
-
"output": {
|
|
315
|
-
"type": "text",
|
|
316
|
-
"value": "{\"success\":true,\"agent\":\"backend-dev\",\"result\":\"## Backend Developer — 一句话概括\\n\\n我能**从零搭建完整后端服务**,涵盖 RESTful API 设计、数据库建模(MySQL/PostgreSQL/MongoDB)、认证授权(JWT/OAuth2)、缓存策略(Redis)、消息队列、微服务拆分与高并发优化,输出可直接上线的服务端代码。\"}"
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
]
|
|
320
|
-
},
|
|
321
|
-
{
|
|
322
|
-
"role": "assistant",
|
|
323
|
-
"content": [
|
|
324
|
-
{
|
|
325
|
-
"type": "reasoning",
|
|
326
|
-
"text": "Both subagents returned results in parallel. This demonstrates clearly that I can handle multiple subagent calls simultaneously. Let me present the results to the user."
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
"type": "text",
|
|
330
|
-
"text": "两个子 Agent 在同一时间并行执行并返回了结果:\n\n- **前端开发专家**:专注于用 React/Vue/TypeScript 构建高性能组件、实现响应式界面、优化加载性能。\n- **后端开发专家**:能从零搭建完整后端服务,涵盖 RESTful API 设计、数据库建模、认证授权、缓存策略、消息队列等。\n\n只要任务之间没有依赖关系,我就可以一次性并行派出多个子 Agent,效率直接翻倍。有需要并行处理的任务尽管来!"
|
|
331
|
-
}
|
|
332
|
-
]
|
|
72
|
+
"role": "user",
|
|
73
|
+
"content": "你好"
|
|
333
74
|
}
|
|
334
75
|
],
|
|
335
76
|
"variables": {},
|
|
336
77
|
"metadata": {
|
|
337
|
-
"createdAt":
|
|
338
|
-
"lastActive":
|
|
78
|
+
"createdAt": 1777342617308,
|
|
79
|
+
"lastActive": 1777342617308,
|
|
339
80
|
"messageCount": 0,
|
|
340
81
|
"compressionCount": 0
|
|
341
82
|
}
|
|
@@ -203,7 +203,10 @@
|
|
|
203
203
|
"Bash(node -e \"\nconst poster = require\\('/d/code/foliko-plugins/poster-plugin'\\);\nconsole.log\\('poster.tools:', typeof poster.tools\\);\nconsole.log\\('poster.tools keys:', Object.keys\\(poster.tools || {}\\).slice\\(0, 5\\)\\);\n\" 2>&1)",
|
|
204
204
|
"Bash(node -e \"require\\('./src/core/agent-chat.js'\\); console.log\\('OK'\\)\" 2>&1)",
|
|
205
205
|
"Bash(node -e \"const ai = require\\('ai'\\); console.log\\('tool type:', typeof ai.tool\\)\")",
|
|
206
|
-
"mcp__MiniMax__web_search"
|
|
206
|
+
"mcp__MiniMax__web_search",
|
|
207
|
+
"Bash(npm test:*)",
|
|
208
|
+
"Bash(node -e \"require\\('./src/index.js'\\); console.log\\('Module loaded successfully'\\)\")",
|
|
209
|
+
"Bash(node examples/basic.js 2>&1)"
|
|
207
210
|
]
|
|
208
211
|
}
|
|
209
212
|
}
|
package/cli/src/ui/chat-ui.js
CHANGED
|
@@ -95,7 +95,7 @@ class ChatUI {
|
|
|
95
95
|
printWelcome() {
|
|
96
96
|
console.log(`${colored('Foliko', CYAN)} - 持续对话聊天`);
|
|
97
97
|
console.log(
|
|
98
|
-
`${colored('Ctrl+C', DIM)} 退出 | ${colored('连续两次回车', DIM)} 发送多行 | ${colored('!!', DIM)} 立即发送\n`
|
|
98
|
+
`${colored('Ctrl+C', DIM)} 退出 | ${colored('/clean', DIM)} 清除上下文 | ${colored('连续两次回车', DIM)} 发送多行 | ${colored('!!', DIM)} 立即发送\n`
|
|
99
99
|
);
|
|
100
100
|
}
|
|
101
101
|
|
|
@@ -152,6 +152,34 @@ class ChatUI {
|
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
// 清除上下文命令
|
|
156
|
+
if (input.toLowerCase() === '/clean' || input.toLowerCase() === '/clear') {
|
|
157
|
+
const { sessionId } = this;
|
|
158
|
+
// 清除 AgentChatHandler 的消息存储(需要传 sessionId)
|
|
159
|
+
if (this.agent._chatHandler) {
|
|
160
|
+
this.agent._chatHandler.clearHistory(sessionId);
|
|
161
|
+
}
|
|
162
|
+
// 清除 SessionContext 的消息和压缩计数
|
|
163
|
+
if (this.agent.framework) {
|
|
164
|
+
const sessionCtx = this.agent.framework.getSessionContext(sessionId);
|
|
165
|
+
if (sessionCtx) {
|
|
166
|
+
sessionCtx.clearMessages();
|
|
167
|
+
sessionCtx.compressionState.count = 0;
|
|
168
|
+
sessionCtx.metadata.compressionCount = 0;
|
|
169
|
+
}
|
|
170
|
+
// 同步清除 framework 缓存的 sessionContexts
|
|
171
|
+
const cachedCtx = this.agent.framework._sessionContexts?.get(sessionId);
|
|
172
|
+
if (cachedCtx) {
|
|
173
|
+
cachedCtx.clearMessages();
|
|
174
|
+
cachedCtx.compressionState.count = 0;
|
|
175
|
+
cachedCtx.metadata.compressionCount = 0;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
console.log(`${colored('[提示]', CYAN)} 对话上下文已清除\n`);
|
|
179
|
+
await this.promptUser();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
155
183
|
// 发送消息
|
|
156
184
|
await this.sendMessage(input);
|
|
157
185
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foliko",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.38",
|
|
4
4
|
"description": "简约的插件化 Agent 框架",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"marked-terminal": "6",
|
|
72
72
|
"node-cron": "^4.2.1",
|
|
73
73
|
"node-html-markdown": "^1.3.0",
|
|
74
|
+
"node-schedule": "^2.1.1",
|
|
74
75
|
"node-telegram-bot-api": "^0.67.0",
|
|
75
76
|
"nodemailer": "^6.10.0",
|
|
76
77
|
"qrcode-terminal": "^0.12.0",
|
package/plugins/feishu-plugin.js
CHANGED
|
@@ -192,12 +192,13 @@ class FeishuPlugin extends Plugin {
|
|
|
192
192
|
case 'start':
|
|
193
193
|
case 'help':
|
|
194
194
|
await this._sendMessage(openId,
|
|
195
|
-
'👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/
|
|
195
|
+
'👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/clean - 清除对话上下文\n/history - 查看历史消息数',
|
|
196
196
|
originalMsg)
|
|
197
197
|
break
|
|
198
198
|
case 'clear':
|
|
199
|
-
|
|
200
|
-
await this.
|
|
199
|
+
case 'clean':
|
|
200
|
+
await this._clearSessionContext(openId)
|
|
201
|
+
await this._sendMessage(openId, '✅ 对话上下文已清除', originalMsg)
|
|
201
202
|
break
|
|
202
203
|
case 'history':
|
|
203
204
|
if (this._sessionPlugin) {
|
|
@@ -442,6 +443,38 @@ class FeishuPlugin extends Plugin {
|
|
|
442
443
|
}
|
|
443
444
|
}
|
|
444
445
|
|
|
446
|
+
/**
|
|
447
|
+
* 清除会话上下文(清除消息历史和压缩计数)
|
|
448
|
+
*/
|
|
449
|
+
async _clearSessionContext(openId) {
|
|
450
|
+
const sessionId = `feishu_${openId}`
|
|
451
|
+
|
|
452
|
+
// 获取或创建 sessionAgent
|
|
453
|
+
const { agent } = this._getSessionAgent(openId)
|
|
454
|
+
|
|
455
|
+
// 清除 AgentChatHandler 的消息存储(需要传 sessionId)
|
|
456
|
+
if (agent._chatHandler) {
|
|
457
|
+
agent._chatHandler.clearHistory(sessionId)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// 清除 SessionContext 的消息和压缩计数
|
|
461
|
+
if (this._framework) {
|
|
462
|
+
const sessionCtx = this._framework.getSessionContext(sessionId)
|
|
463
|
+
if (sessionCtx) {
|
|
464
|
+
sessionCtx.clearMessages()
|
|
465
|
+
sessionCtx.compressionState.count = 0
|
|
466
|
+
sessionCtx.metadata.compressionCount = 0
|
|
467
|
+
}
|
|
468
|
+
// 同步清除 framework 缓存的 sessionContexts
|
|
469
|
+
const cachedCtx = this._framework._sessionContexts?.get(sessionId)
|
|
470
|
+
if (cachedCtx) {
|
|
471
|
+
cachedCtx.clearMessages()
|
|
472
|
+
cachedCtx.compressionState.count = 0
|
|
473
|
+
cachedCtx.metadata.compressionCount = 0
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
445
478
|
getStatus() {
|
|
446
479
|
let sessions = []
|
|
447
480
|
if (this._sessionPlugin) {
|
|
@@ -11,12 +11,12 @@ const { z } = require('zod')
|
|
|
11
11
|
const fs = require('fs')
|
|
12
12
|
const path = require('path')
|
|
13
13
|
|
|
14
|
-
// 尝试加载 node-
|
|
15
|
-
let
|
|
14
|
+
// 尝试加载 node-schedule
|
|
15
|
+
let schedule = null
|
|
16
16
|
try {
|
|
17
|
-
|
|
17
|
+
schedule = require('node-schedule')
|
|
18
18
|
} catch (e) {
|
|
19
|
-
log.warn(' node-
|
|
19
|
+
log.warn(' node-schedule not installed, cron tasks will not work')
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// ============================================================================
|
|
@@ -200,8 +200,8 @@ class SchedulerPlugin extends Plugin {
|
|
|
200
200
|
const isCron = /^[\d*,\/-\s]+$/.test(scheduleTime) && scheduleTime.split(' ').length >= 5
|
|
201
201
|
|
|
202
202
|
if (isCron || repeat) {
|
|
203
|
-
if (!
|
|
204
|
-
return { success: false, error: 'node-
|
|
203
|
+
if (!schedule) {
|
|
204
|
+
return { success: false, error: 'node-schedule not installed' }
|
|
205
205
|
}
|
|
206
206
|
const task = {
|
|
207
207
|
id: taskId,
|
|
@@ -219,7 +219,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
219
219
|
shell,
|
|
220
220
|
notify: args.notify !== false
|
|
221
221
|
}
|
|
222
|
-
task.cronTask =
|
|
222
|
+
task.cronTask = schedule.scheduleJob(task.cronExpression, async () => {
|
|
223
223
|
await this._executeTask(task)
|
|
224
224
|
})
|
|
225
225
|
this._tasks.set(task.id, task)
|
|
@@ -337,8 +337,8 @@ class SchedulerPlugin extends Plugin {
|
|
|
337
337
|
|
|
338
338
|
if (isCron || repeat) {
|
|
339
339
|
// Cron 任务
|
|
340
|
-
if (!
|
|
341
|
-
return { success: false, error: 'node-
|
|
340
|
+
if (!schedule) {
|
|
341
|
+
return { success: false, error: 'node-schedule not installed' }
|
|
342
342
|
}
|
|
343
343
|
task = {
|
|
344
344
|
id: taskId,
|
|
@@ -357,8 +357,8 @@ class SchedulerPlugin extends Plugin {
|
|
|
357
357
|
notify: args.notify !== false // 默认发送通知
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
-
// 使用 node-
|
|
361
|
-
task.cronTask =
|
|
360
|
+
// 使用 node-schedule 调度
|
|
361
|
+
task.cronTask = schedule.scheduleJob(task.cronExpression, async () => {
|
|
362
362
|
await this._executeTask(task)
|
|
363
363
|
})
|
|
364
364
|
} else if (scheduleTime.includes(':')) {
|
|
@@ -586,9 +586,9 @@ class SchedulerPlugin extends Plugin {
|
|
|
586
586
|
let task = { ...saved }
|
|
587
587
|
|
|
588
588
|
// 重新调度 Cron 任务
|
|
589
|
-
if (task.type === 'cron' && task.enabled &&
|
|
589
|
+
if (task.type === 'cron' && task.enabled && schedule) {
|
|
590
590
|
try {
|
|
591
|
-
task.cronTask =
|
|
591
|
+
task.cronTask = schedule.scheduleJob(task.cronExpression, async () => {
|
|
592
592
|
await this._executeTask(task)
|
|
593
593
|
})
|
|
594
594
|
this._tasks.set(task.id, task)
|
|
@@ -599,9 +599,9 @@ class SchedulerPlugin extends Plugin {
|
|
|
599
599
|
}
|
|
600
600
|
}
|
|
601
601
|
// 重新调度 Cron Shell 任务
|
|
602
|
-
else if (task.type === 'shell' && task.enabled && task.cronExpression &&
|
|
602
|
+
else if (task.type === 'shell' && task.enabled && task.cronExpression && schedule) {
|
|
603
603
|
try {
|
|
604
|
-
task.cronTask =
|
|
604
|
+
task.cronTask = schedule.scheduleJob(task.cronExpression, async () => {
|
|
605
605
|
await this._executeTask(task)
|
|
606
606
|
})
|
|
607
607
|
this._tasks.set(task.id, task)
|
|
@@ -651,7 +651,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
651
651
|
task.timer = null
|
|
652
652
|
}
|
|
653
653
|
if (task.cronTask) {
|
|
654
|
-
task.cronTask.
|
|
654
|
+
task.cronTask.cancel()
|
|
655
655
|
task.cronTask = null
|
|
656
656
|
}
|
|
657
657
|
task.enabled = false
|
|
@@ -820,7 +820,7 @@ class SchedulerPlugin extends Plugin {
|
|
|
820
820
|
task.timer = null
|
|
821
821
|
}
|
|
822
822
|
if (task.cronTask) {
|
|
823
|
-
task.cronTask.
|
|
823
|
+
task.cronTask.cancel()
|
|
824
824
|
task.cronTask = null
|
|
825
825
|
}
|
|
826
826
|
this._tasks.delete(taskId)
|
|
@@ -302,20 +302,24 @@ class TelegramPlugin extends Plugin {
|
|
|
302
302
|
|
|
303
303
|
switch (command.toLowerCase()) {
|
|
304
304
|
case 'start':
|
|
305
|
+
case 'help':
|
|
305
306
|
await this._sendMessage(chatId,
|
|
306
|
-
'👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/
|
|
307
|
+
'👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/clean - 清除对话上下文\n/history - 查看历史消息数',
|
|
307
308
|
msg.message_id)
|
|
308
309
|
break
|
|
310
|
+
case 'clean':
|
|
309
311
|
case 'clear':
|
|
310
|
-
this.
|
|
311
|
-
await this._sendMessage(chatId, '✅
|
|
312
|
+
await this._clearSessionContext(chatId)
|
|
313
|
+
await this._sendMessage(chatId, '✅ 对话上下文已清除', msg.message_id)
|
|
312
314
|
break
|
|
313
315
|
case 'history':
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const
|
|
316
|
+
const sessionInfo = this._getSessionAgent(chatId)
|
|
317
|
+
if (sessionInfo && this._framework) {
|
|
318
|
+
const sessionCtx = this._framework.getSessionContext(sessionInfo.sessionId)
|
|
319
|
+
const count = sessionCtx?.messageStore?.messages?.length || 0
|
|
320
|
+
const compressionCount = sessionCtx?.metadata?.compressionCount || 0
|
|
317
321
|
await this._sendMessage(chatId,
|
|
318
|
-
`📊 当前状态\n\n
|
|
322
|
+
`📊 当前状态\n\n历史消息:${count}\n压缩次数:${compressionCount}`,
|
|
319
323
|
msg.message_id)
|
|
320
324
|
} else {
|
|
321
325
|
await this._sendMessage(chatId, '❌ 会话服务不可用', msg.message_id)
|
|
@@ -452,6 +456,38 @@ class TelegramPlugin extends Plugin {
|
|
|
452
456
|
}
|
|
453
457
|
}
|
|
454
458
|
|
|
459
|
+
/**
|
|
460
|
+
* 清除会话上下文(清除消息历史和压缩计数)
|
|
461
|
+
*/
|
|
462
|
+
async _clearSessionContext(chatId) {
|
|
463
|
+
const sessionId = `telegram_${chatId}`
|
|
464
|
+
|
|
465
|
+
// 获取或创建 sessionAgent
|
|
466
|
+
const { agent } = this._getSessionAgent(chatId)
|
|
467
|
+
|
|
468
|
+
// 清除 AgentChatHandler 的消息存储(需要传 sessionId)
|
|
469
|
+
if (agent._chatHandler) {
|
|
470
|
+
agent._chatHandler.clearHistory(sessionId)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// 清除 SessionContext 的消息和压缩计数
|
|
474
|
+
if (this._framework) {
|
|
475
|
+
const sessionCtx = this._framework.getSessionContext(sessionId)
|
|
476
|
+
if (sessionCtx) {
|
|
477
|
+
sessionCtx.clearMessages()
|
|
478
|
+
sessionCtx.compressionState.count = 0
|
|
479
|
+
sessionCtx.metadata.compressionCount = 0
|
|
480
|
+
}
|
|
481
|
+
// 同步清除 framework 缓存的 sessionContexts
|
|
482
|
+
const cachedCtx = this._framework._sessionContexts?.get(sessionId)
|
|
483
|
+
if (cachedCtx) {
|
|
484
|
+
cachedCtx.clearMessages()
|
|
485
|
+
cachedCtx.compressionState.count = 0
|
|
486
|
+
cachedCtx.metadata.compressionCount = 0
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
455
491
|
async _sendMessage(chatId, text, replyToMessageId = null) {
|
|
456
492
|
if (!this._bot) return
|
|
457
493
|
return this._bot.sendMessage(chatId, text, { reply_to_message_id: replyToMessageId })
|
package/plugins/weixin-plugin.js
CHANGED
|
@@ -535,6 +535,12 @@ class WeixinPlugin extends Plugin {
|
|
|
535
535
|
* 处理对话
|
|
536
536
|
*/
|
|
537
537
|
async _processChat(userId, text, originalMsg) {
|
|
538
|
+
// 处理命令
|
|
539
|
+
if (text.startsWith('/')) {
|
|
540
|
+
await this._handleCommand(userId, text, originalMsg)
|
|
541
|
+
return
|
|
542
|
+
}
|
|
543
|
+
|
|
538
544
|
const sessionInfo = this._getSessionAgent(userId)
|
|
539
545
|
if (!sessionInfo) {
|
|
540
546
|
log.error(' No session agent available')
|
|
@@ -561,6 +567,75 @@ class WeixinPlugin extends Plugin {
|
|
|
561
567
|
}
|
|
562
568
|
}
|
|
563
569
|
|
|
570
|
+
/**
|
|
571
|
+
* 处理命令
|
|
572
|
+
*/
|
|
573
|
+
async _handleCommand(userId, text, originalMsg) {
|
|
574
|
+
const parts = text.split(' ')
|
|
575
|
+
const command = parts[0].substring(1)
|
|
576
|
+
const args = parts.slice(1).join(' ')
|
|
577
|
+
|
|
578
|
+
switch (command.toLowerCase()) {
|
|
579
|
+
case 'start':
|
|
580
|
+
case 'help':
|
|
581
|
+
await this._sendMessageBatch(originalMsg, userId,
|
|
582
|
+
'👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/clean - 清除对话上下文\n/history - 查看历史消息数',
|
|
583
|
+
true)
|
|
584
|
+
break
|
|
585
|
+
case 'clean':
|
|
586
|
+
case 'clear':
|
|
587
|
+
await this._clearSessionContext(userId)
|
|
588
|
+
await this._sendMessageBatch(originalMsg, userId, '✅ 对话上下文已清除', true)
|
|
589
|
+
break
|
|
590
|
+
case 'history':
|
|
591
|
+
const sessionInfo = this._getSessionAgent(userId)
|
|
592
|
+
if (sessionInfo && this._framework) {
|
|
593
|
+
const sessionCtx = this._framework.getSessionContext(sessionInfo.sessionId)
|
|
594
|
+
const count = sessionCtx?.messageStore?.messages?.length || 0
|
|
595
|
+
const compressionCount = sessionCtx?.metadata?.compressionCount || 0
|
|
596
|
+
await this._sendMessageBatch(originalMsg, userId,
|
|
597
|
+
`📊 当前状态\n\n历史消息:${count}\n压缩次数:${compressionCount}`,
|
|
598
|
+
true)
|
|
599
|
+
}
|
|
600
|
+
break
|
|
601
|
+
default:
|
|
602
|
+
// 未识别命令,当作普通消息处理
|
|
603
|
+
await this._processChat(userId, text, originalMsg)
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* 清除会话上下文(清除消息历史和压缩计数)
|
|
609
|
+
*/
|
|
610
|
+
async _clearSessionContext(userId) {
|
|
611
|
+
const sessionId = `weixin_${userId}`
|
|
612
|
+
|
|
613
|
+
// 获取或创建 sessionAgent
|
|
614
|
+
const { agent } = this._getSessionAgent(userId)
|
|
615
|
+
|
|
616
|
+
// 清除 AgentChatHandler 的消息存储(需要传 sessionId)
|
|
617
|
+
if (agent._chatHandler) {
|
|
618
|
+
agent._chatHandler.clearHistory(sessionId)
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// 清除 SessionContext 的消息和压缩计数
|
|
622
|
+
if (this._framework) {
|
|
623
|
+
const sessionCtx = this._framework.getSessionContext(sessionId)
|
|
624
|
+
if (sessionCtx) {
|
|
625
|
+
sessionCtx.clearMessages()
|
|
626
|
+
sessionCtx.compressionState.count = 0
|
|
627
|
+
sessionCtx.metadata.compressionCount = 0
|
|
628
|
+
}
|
|
629
|
+
// 同步清除 framework 缓存的 sessionContexts
|
|
630
|
+
const cachedCtx = this._framework._sessionContexts?.get(sessionId)
|
|
631
|
+
if (cachedCtx) {
|
|
632
|
+
cachedCtx.clearMessages()
|
|
633
|
+
cachedCtx.compressionState.count = 0
|
|
634
|
+
cachedCtx.metadata.compressionCount = 0
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
564
639
|
/**
|
|
565
640
|
* 权限检查
|
|
566
641
|
*/
|
package/src/core/agent-chat.js
CHANGED
|
@@ -47,11 +47,16 @@ class AgentChatHandler extends EventEmitter {
|
|
|
47
47
|
this.baseURL = config.baseURL;
|
|
48
48
|
this.providerOptions = config.providerOptions || {};
|
|
49
49
|
this.providerOptions.maxOutputTokens = config.providerOptions?.maxOutputTokens || 8192;
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
|
|
51
|
+
// DeepSeek thinking mode: 支持通过 config.thinkingMode 控制开关
|
|
52
|
+
// config.thinkingMode = true 可启用,默认为禁用
|
|
53
|
+
const enableThinking = config.thinkingMode === true && isThinkingModel(this.model);
|
|
54
|
+
this._thinkingMode = enableThinking;
|
|
55
|
+
|
|
56
|
+
if (enableThinking) {
|
|
57
|
+
// thinking mode 模型不支持 temperature 参数
|
|
52
58
|
this.providerOptions.temperature = undefined;
|
|
53
59
|
// DeepSeek 需要通过 extra_body 启用 thinking mode
|
|
54
|
-
// AI SDK 会将 providerOptions.deepseek 映射到请求体的 extra_body
|
|
55
60
|
if (!this.providerOptions.deepseek) {
|
|
56
61
|
this.providerOptions.deepseek = {};
|
|
57
62
|
}
|
|
@@ -465,7 +470,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
465
470
|
};
|
|
466
471
|
|
|
467
472
|
// DeepSeek thinking mode: 收集 reasoning_content
|
|
468
|
-
const isThinking =
|
|
473
|
+
const isThinking = this._thinkingMode;
|
|
469
474
|
let reasoningContent = '';
|
|
470
475
|
|
|
471
476
|
const result = await framework.runInSession(sessionId, {}, async () => {
|
|
@@ -579,7 +584,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
579
584
|
|
|
580
585
|
// DeepSeek thinking mode: 处理参数
|
|
581
586
|
const apiOptions = { ...this.providerOptions };
|
|
582
|
-
if (
|
|
587
|
+
if (this._thinkingMode) {
|
|
583
588
|
delete apiOptions.temperature;
|
|
584
589
|
}
|
|
585
590
|
|
|
@@ -600,7 +605,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
600
605
|
}
|
|
601
606
|
|
|
602
607
|
// DeepSeek thinking mode: 处理 reasoning_content
|
|
603
|
-
const isThinking =
|
|
608
|
+
const isThinking = this._thinkingMode;
|
|
604
609
|
if (isThinking && result.reasoningText) {
|
|
605
610
|
// 有 reasoning_text 需要添加到消息中(当有 tool_calls 时)
|
|
606
611
|
const finishMessages = result.response.messages;
|
package/src/core/agent.js
CHANGED
|
@@ -90,10 +90,11 @@ class Agent extends EventEmitter {
|
|
|
90
90
|
this.apiKey = config.apiKey;
|
|
91
91
|
this.baseURL = config.baseURL;
|
|
92
92
|
this.provider = config.provider || 'deepseek';
|
|
93
|
-
this.providerOptions =
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
this.providerOptions = {
|
|
94
|
+
...config.providerOptions,
|
|
95
|
+
maxOutputTokens: config.providerOptions?.maxOutputTokens ?? AGENT_CONFIG.MAX_OUTPUT_TOKENS,
|
|
96
|
+
temperature: config.providerOptions?.temperature ?? AGENT_CONFIG.TEMPERATURE,
|
|
97
|
+
};
|
|
97
98
|
// 原始 system prompt
|
|
98
99
|
this._originalPrompt =
|
|
99
100
|
config.systemPrompt ||
|
|
@@ -188,10 +188,15 @@ class ContextCompressor {
|
|
|
188
188
|
|
|
189
189
|
// 更新压缩状态
|
|
190
190
|
this._compressionCount++;
|
|
191
|
+
const tokenCount = this._countMessagesTokens(messages);
|
|
191
192
|
if (messageStore.compressionState) {
|
|
192
193
|
messageStore.compressionState.count++;
|
|
193
194
|
messageStore.compressionState.lastCompressedAt = Date.now();
|
|
194
|
-
messageStore.compressionState.lastTokenCount =
|
|
195
|
+
messageStore.compressionState.lastTokenCount = tokenCount;
|
|
196
|
+
}
|
|
197
|
+
// 同步更新 SessionContext.metadata.compressionCount(如果可用)
|
|
198
|
+
if (typeof messageStore.recordCompression === 'function') {
|
|
199
|
+
messageStore.recordCompression(tokenCount);
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
logger.info(
|
|
@@ -19,10 +19,6 @@ class ContextManager {
|
|
|
19
19
|
|
|
20
20
|
// Context 存储
|
|
21
21
|
this._requestContexts = new Map(); // requestId → RequestContext
|
|
22
|
-
this._sessionContexts = new Map(); // sessionId → SessionContext
|
|
23
|
-
|
|
24
|
-
// 引用计数(用于自动清理)
|
|
25
|
-
this._refCounts = new Map();
|
|
26
22
|
|
|
27
23
|
// 事件转发
|
|
28
24
|
this._setupEventForwarding();
|
|
@@ -88,86 +84,15 @@ class ContextManager {
|
|
|
88
84
|
this._refCounts.set(key, (this._refCounts.get(key) || 0) + 1);
|
|
89
85
|
}
|
|
90
86
|
|
|
91
|
-
// ==================== Session Context ====================
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* 获取或创建 Session Context
|
|
95
|
-
* @param {string} sessionId - 会话 ID
|
|
96
|
-
* @param {Object} options - 配置选项
|
|
97
|
-
* @returns {SessionContext}
|
|
98
|
-
*/
|
|
99
|
-
getOrCreateSessionContext(sessionId, options = {}) {
|
|
100
|
-
let ctx = this._sessionContexts.get(sessionId);
|
|
101
|
-
if (!ctx) {
|
|
102
|
-
ctx = new SessionContext(sessionId, this.framework, options);
|
|
103
|
-
this._sessionContexts.set(sessionId, ctx);
|
|
104
|
-
this._refCounts.set(`session:${sessionId}`, 1);
|
|
105
|
-
} else {
|
|
106
|
-
// 增加引用计数
|
|
107
|
-
const key = `session:${sessionId}`;
|
|
108
|
-
this._refCounts.set(key, (this._refCounts.get(key) || 0) + 1);
|
|
109
|
-
}
|
|
110
|
-
return ctx;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* 获取 Session Context
|
|
115
|
-
* @param {string} sessionId - 会话 ID
|
|
116
|
-
* @returns {SessionContext|null}
|
|
117
|
-
*/
|
|
118
|
-
getSessionContext(sessionId) {
|
|
119
|
-
return this._sessionContexts.get(sessionId) || null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* 释放 Session Context(减少引用计数)
|
|
124
|
-
* @param {string} sessionId - 会话 ID
|
|
125
|
-
* @param {boolean} [force=false] - 强制销毁
|
|
126
|
-
*/
|
|
127
|
-
releaseSessionContext(sessionId, force = false) {
|
|
128
|
-
const key = `session:${sessionId}`;
|
|
129
|
-
const count = this._refCounts.get(key) || 0;
|
|
130
|
-
|
|
131
|
-
if (force || count <= 1) {
|
|
132
|
-
const ctx = this._sessionContexts.get(sessionId);
|
|
133
|
-
if (ctx) {
|
|
134
|
-
ctx.destroy();
|
|
135
|
-
this.framework.emit('session:context-destroyed', ctx);
|
|
136
|
-
}
|
|
137
|
-
this._sessionContexts.delete(sessionId);
|
|
138
|
-
this._refCounts.delete(key);
|
|
139
|
-
} else {
|
|
140
|
-
this._refCounts.set(key, count - 1);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* 增加 Session Context 引用计数
|
|
146
|
-
* @param {string} sessionId - 会话 ID
|
|
147
|
-
*/
|
|
148
|
-
retainSessionContext(sessionId) {
|
|
149
|
-
const key = `session:${sessionId}`;
|
|
150
|
-
this._refCounts.set(key, (this._refCounts.get(key) || 0) + 1);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* 检查 Session Context 是否存在
|
|
155
|
-
* @param {string} sessionId - 会话 ID
|
|
156
|
-
* @returns {boolean}
|
|
157
|
-
*/
|
|
158
|
-
hasSessionContext(sessionId) {
|
|
159
|
-
return this._sessionContexts.has(sessionId);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
87
|
// ==================== 批量操作 ====================
|
|
163
88
|
|
|
164
89
|
/**
|
|
165
90
|
* 销毁所有 Session Context
|
|
91
|
+
* @deprecated Use framework.destroySessionContext() instead - Framework manages sessions
|
|
166
92
|
*/
|
|
167
93
|
destroyAllSessionContexts() {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
94
|
+
// No-op: Framework manages session contexts, not ContextManager
|
|
95
|
+
// This method exists for API compatibility but does nothing
|
|
171
96
|
}
|
|
172
97
|
|
|
173
98
|
/**
|
|
@@ -192,8 +117,7 @@ class ContextManager {
|
|
|
192
117
|
this.releaseRequestContext(requestId);
|
|
193
118
|
}
|
|
194
119
|
|
|
195
|
-
//
|
|
196
|
-
this.destroyAllSessionContexts();
|
|
120
|
+
// Session Contexts 由 Framework 管理,此处不需要清理
|
|
197
121
|
}
|
|
198
122
|
|
|
199
123
|
// ==================== 统计 ====================
|
|
@@ -205,8 +129,6 @@ class ContextManager {
|
|
|
205
129
|
getStats() {
|
|
206
130
|
return {
|
|
207
131
|
activeRequests: this._requestContexts.size,
|
|
208
|
-
activeSessions: this._sessionContexts.size,
|
|
209
|
-
refCounts: Object.fromEntries(this._refCounts),
|
|
210
132
|
};
|
|
211
133
|
}
|
|
212
134
|
|
|
@@ -215,19 +137,8 @@ class ContextManager {
|
|
|
215
137
|
* @returns {Object}
|
|
216
138
|
*/
|
|
217
139
|
getMemoryUsage() {
|
|
218
|
-
let sessionMessagesSize = 0;
|
|
219
|
-
let sessionVariablesSize = 0;
|
|
220
|
-
|
|
221
|
-
for (const ctx of this._sessionContexts.values()) {
|
|
222
|
-
sessionMessagesSize += ctx.messageStore.messages.length;
|
|
223
|
-
sessionVariablesSize += ctx.variables.size;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
140
|
return {
|
|
227
141
|
requestContexts: this._requestContexts.size,
|
|
228
|
-
sessionContexts: this._sessionContexts.size,
|
|
229
|
-
totalSessionMessages: sessionMessagesSize,
|
|
230
|
-
totalSessionVariables: sessionVariablesSize,
|
|
231
142
|
};
|
|
232
143
|
}
|
|
233
144
|
}
|
package/src/core/framework.js
CHANGED
|
@@ -96,7 +96,12 @@ class Framework extends EventEmitter {
|
|
|
96
96
|
this.toolRegistry.on('tool:call', toolCallHandler);
|
|
97
97
|
this.toolRegistry.on('tool:result', toolResultHandler);
|
|
98
98
|
this.toolRegistry.on('tool:error', toolErrorHandler);
|
|
99
|
-
this._toolRegistryListeners = [
|
|
99
|
+
this._toolRegistryListeners = [
|
|
100
|
+
{ event: 'tool:registered', handler: toolRegisteredHandler },
|
|
101
|
+
{ event: 'tool:call', handler: toolCallHandler },
|
|
102
|
+
{ event: 'tool:result', handler: toolResultHandler },
|
|
103
|
+
{ event: 'tool:error', handler: toolErrorHandler },
|
|
104
|
+
];
|
|
100
105
|
|
|
101
106
|
// 事件转发 - Agent 事件
|
|
102
107
|
this._setupAgentEventForwarding();
|
|
@@ -683,15 +688,18 @@ class Framework extends EventEmitter {
|
|
|
683
688
|
// 只同步 Subagent
|
|
684
689
|
if (!agent._isSubagent) continue;
|
|
685
690
|
|
|
691
|
+
// Build a Set of already registered parts for O(1) lookup
|
|
692
|
+
const registeredNames = new Set(agent._promptParts?.map(p => p.name) || []);
|
|
693
|
+
|
|
686
694
|
for (const [name, part] of mainParts) {
|
|
687
695
|
// 子 Agent 跳过这些不适合的部分
|
|
688
696
|
if (['original-prompt', 'shared-prompt', 'datetime', 'metadata', 'capabilities'].includes(name)) {
|
|
689
697
|
continue;
|
|
690
698
|
}
|
|
691
|
-
//
|
|
692
|
-
|
|
693
|
-
if (!alreadyRegistered) {
|
|
699
|
+
// O(1) lookup instead of O(m)
|
|
700
|
+
if (!registeredNames.has(name)) {
|
|
694
701
|
agent.registerPromptPart(name, part.priority, part.provider);
|
|
702
|
+
registeredNames.add(name);
|
|
695
703
|
}
|
|
696
704
|
}
|
|
697
705
|
}
|
|
@@ -912,11 +920,8 @@ class Framework extends EventEmitter {
|
|
|
912
920
|
|
|
913
921
|
// 移除 toolRegistry 的事件监听器
|
|
914
922
|
if (this._toolRegistryListeners) {
|
|
915
|
-
for (const
|
|
916
|
-
this.toolRegistry.off(
|
|
917
|
-
this.toolRegistry.off('tool:call', listener);
|
|
918
|
-
this.toolRegistry.off('tool:result', listener);
|
|
919
|
-
this.toolRegistry.off('tool:error', listener);
|
|
923
|
+
for (const { event, handler } of this._toolRegistryListeners) {
|
|
924
|
+
this.toolRegistry.off(event, handler);
|
|
920
925
|
}
|
|
921
926
|
this._toolRegistryListeners = [];
|
|
922
927
|
}
|
|
@@ -52,6 +52,8 @@ class SessionContext {
|
|
|
52
52
|
messages: options.initialMessages || [], // 对话历史
|
|
53
53
|
historyLoaded: false, // 是否已从持久化加载
|
|
54
54
|
systemPrompt: null, // 当前系统提示词
|
|
55
|
+
// 委托给 SessionContext.recordCompression
|
|
56
|
+
recordCompression: (tokenCount) => this.recordCompression(tokenCount),
|
|
55
57
|
};
|
|
56
58
|
|
|
57
59
|
// 压缩状态
|
|
@@ -59,6 +61,7 @@ class SessionContext {
|
|
|
59
61
|
lastCompressedAt: null,
|
|
60
62
|
lastTokenCount: 0,
|
|
61
63
|
pendingCompression: false,
|
|
64
|
+
count: 0,
|
|
62
65
|
};
|
|
63
66
|
|
|
64
67
|
// 自定义数据插槽
|
|
@@ -200,6 +203,7 @@ class SessionContext {
|
|
|
200
203
|
initialMessages: data.messages || [],
|
|
201
204
|
variables: data.variables || {},
|
|
202
205
|
metadata: data.metadata || {},
|
|
206
|
+
storage,
|
|
203
207
|
});
|
|
204
208
|
|
|
205
209
|
ctx.messageStore.historyLoaded = true;
|
|
@@ -285,6 +289,7 @@ class SessionContext {
|
|
|
285
289
|
recordCompression(tokenCount) {
|
|
286
290
|
this.compressionState.lastCompressedAt = Date.now();
|
|
287
291
|
this.compressionState.lastTokenCount = tokenCount;
|
|
292
|
+
this.compressionState.count++;
|
|
288
293
|
this.metadata.compressionCount++;
|
|
289
294
|
}
|
|
290
295
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
11
|
+
const fsPromises = require('fs').promises;
|
|
11
12
|
const path = require('path');
|
|
12
13
|
|
|
13
14
|
class SessionStorageAdapter {
|
|
@@ -36,10 +37,12 @@ class SessionStorageAdapter {
|
|
|
36
37
|
* 确保目录存在
|
|
37
38
|
* @private
|
|
38
39
|
*/
|
|
39
|
-
_ensureDirectory() {
|
|
40
|
+
async _ensureDirectory() {
|
|
40
41
|
const dir = path.resolve(process.cwd(), this.baseDir);
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
try {
|
|
43
|
+
await fsPromises.access(dir);
|
|
44
|
+
} catch {
|
|
45
|
+
await fsPromises.mkdir(dir, { recursive: true });
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -121,12 +124,14 @@ class SessionStorageAdapter {
|
|
|
121
124
|
try {
|
|
122
125
|
// 确保目录存在
|
|
123
126
|
const dir = path.dirname(filePath);
|
|
124
|
-
|
|
125
|
-
|
|
127
|
+
try {
|
|
128
|
+
await fsPromises.access(dir);
|
|
129
|
+
} catch {
|
|
130
|
+
await fsPromises.mkdir(dir, { recursive: true });
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
const content = JSON.stringify(data, null, 2);
|
|
129
|
-
|
|
134
|
+
await fsPromises.writeFile(filePath, content, 'utf-8');
|
|
130
135
|
} catch (err) {
|
|
131
136
|
console.error(`[SessionStorage] Failed to write ${sessionId}:`, err.message);
|
|
132
137
|
throw err;
|
|
@@ -146,12 +151,14 @@ class SessionStorageAdapter {
|
|
|
146
151
|
if (this.type === 'file') {
|
|
147
152
|
const filePath = this._getFilePath(sessionId);
|
|
148
153
|
|
|
149
|
-
|
|
154
|
+
try {
|
|
155
|
+
await fsPromises.access(filePath);
|
|
156
|
+
} catch {
|
|
150
157
|
return null;
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
try {
|
|
154
|
-
const content =
|
|
161
|
+
const content = await fsPromises.readFile(filePath, 'utf-8');
|
|
155
162
|
return JSON.parse(content);
|
|
156
163
|
} catch (err) {
|
|
157
164
|
console.error(`[SessionStorage] Failed to read ${sessionId}:`, err.message);
|
|
@@ -175,16 +182,19 @@ class SessionStorageAdapter {
|
|
|
175
182
|
if (this.type === 'file') {
|
|
176
183
|
const filePath = this._getFilePath(sessionId);
|
|
177
184
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
try {
|
|
186
|
+
await fsPromises.access(filePath);
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
await fsPromises.unlink(filePath);
|
|
193
|
+
return true;
|
|
194
|
+
} catch (err) {
|
|
195
|
+
console.error(`[SessionStorage] Failed to delete ${sessionId}:`, err.message);
|
|
196
|
+
return false;
|
|
186
197
|
}
|
|
187
|
-
return false;
|
|
188
198
|
}
|
|
189
199
|
|
|
190
200
|
return false;
|
|
@@ -202,12 +212,14 @@ class SessionStorageAdapter {
|
|
|
202
212
|
if (this.type === 'file') {
|
|
203
213
|
const dir = path.resolve(process.cwd(), this.baseDir);
|
|
204
214
|
|
|
205
|
-
|
|
215
|
+
try {
|
|
216
|
+
await fsPromises.access(dir);
|
|
217
|
+
} catch {
|
|
206
218
|
return [];
|
|
207
219
|
}
|
|
208
220
|
|
|
209
221
|
try {
|
|
210
|
-
const files =
|
|
222
|
+
const files = await fsPromises.readdir(dir);
|
|
211
223
|
return files.filter((f) => f.endsWith('.json')).map((f) => f.replace(/\.json$/, ''));
|
|
212
224
|
} catch (err) {
|
|
213
225
|
console.error(`[SessionStorage] Failed to list sessions:`, err.message);
|
|
@@ -255,6 +267,15 @@ let _defaultAdapter = null;
|
|
|
255
267
|
function getDefaultAdapter(options = {}) {
|
|
256
268
|
if (!_defaultAdapter) {
|
|
257
269
|
_defaultAdapter = new SessionStorageAdapter(options);
|
|
270
|
+
} else if (Object.keys(options).length > 0) {
|
|
271
|
+
// 如果已存在但传入了新选项,用新选项更新默认适配器
|
|
272
|
+
// 注意:这会修改全局单例,需要确保调用方知道这一点
|
|
273
|
+
if (options.type && options.type !== _defaultAdapter.type) {
|
|
274
|
+
_defaultAdapter.type = options.type;
|
|
275
|
+
}
|
|
276
|
+
if (options.baseDir && options.baseDir !== _defaultAdapter.baseDir) {
|
|
277
|
+
_defaultAdapter.baseDir = options.baseDir;
|
|
278
|
+
}
|
|
258
279
|
}
|
|
259
280
|
return _defaultAdapter;
|
|
260
281
|
}
|