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.
@@ -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": "call_01_xHeVX63CL4KMNBip1tlngsl4",
183
- "toolName": "search_file",
17
+ "toolCallId": "call_function_dz1v0vse0j9h_1",
18
+ "toolName": "schedule_task",
184
19
  "input": {
185
- "pattern": "deepseek|claude|gpt|qwen|glm|minimax",
186
- "path": "D:\\code\\vb-agent",
187
- "maxResults": 10,
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": "call_00_kC3mIrqFnpDMPc87Pbok5wvx",
199
- "toolName": "read_file",
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,\"pattern\":\"deepseek|claude|gpt|qwen|glm|minimax\",\"results\":[],\"total\":0,\"stats\":{\"filesWithMatches\":0,\"totalMatches\":0,\"searchPath\":\"D:\\\\code\\\\vb-agent\"}}"
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": "reasoning",
221
- "text": "The system prompt doesn't explicitly tell me what model I am, and I can't find it in the configuration files. I should be honest and tell the user that I'm running on the Foliko Agent framework but the specific underlying model isn't exposed to me. Let me check if there's an env file or configuration that might have this info."
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": "assistant",
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": "晕,你能平行处理两个子agent吗?"
56
+ "content": "你好"
266
57
  },
267
58
  {
268
59
  "role": "assistant",
269
60
  "content": [
270
61
  {
271
62
  "type": "reasoning",
272
- "text": "The user is asking if I can parallel process two subagents. Let me think about this. I have `subagent_call` tool which takes `agentName` and `task` parameters. The question is whether I can call two subagents at the same time (in parallel) or if I need to call them sequentially.\n\nLooking at the tools available, `subagent_call` takes a single agent name and task. But I can make multiple function calls in a single response block - so I should be able to call two subagents in parallel by issuing two `subagent_call` invocations in the same function call block.\n\nLet me just tell the user that yes, I can call multiple subagents in parallel by issuing multiple tool calls at once."
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": "可以的!我可以在同一个响应中同时发出多个 `subagent_call`,让两个子 Agent 并行处理任务。\n\n比如像这样同时启动两个:"
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": "tool",
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": 1777019185599,
338
- "lastActive": 1777019185599,
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
  }
@@ -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.35",
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",
@@ -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/clear - 清除对话历史\n/history - 查看历史消息数',
195
+ '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/clean - 清除对话上下文\n/history - 查看历史消息数',
196
196
  originalMsg)
197
197
  break
198
198
  case 'clear':
199
- this._clearSession(openId)
200
- await this._sendMessage(openId, '✅ 对话历史已清除', originalMsg)
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-cron
15
- let cron = null
14
+ // 尝试加载 node-schedule
15
+ let schedule = null
16
16
  try {
17
- cron = require('node-cron')
17
+ schedule = require('node-schedule')
18
18
  } catch (e) {
19
- log.warn(' node-cron not installed, cron tasks will not work')
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 (!cron) {
204
- return { success: false, error: 'node-cron not installed' }
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 = cron.schedule(task.cronExpression, async () => {
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 (!cron) {
341
- return { success: false, error: 'node-cron not installed' }
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-cron 调度
361
- task.cronTask = cron.schedule(task.cronExpression, async () => {
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 && cron) {
589
+ if (task.type === 'cron' && task.enabled && schedule) {
590
590
  try {
591
- task.cronTask = cron.schedule(task.cronExpression, async () => {
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 && cron) {
602
+ else if (task.type === 'shell' && task.enabled && task.cronExpression && schedule) {
603
603
  try {
604
- task.cronTask = cron.schedule(task.cronExpression, async () => {
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.stop()
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.stop()
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/start - 显示帮助\n/clear - 清除对话历史\n/history - 查看历史消息数',
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._clearSession(chatId)
311
- await this._sendMessage(chatId, '✅ 对话历史已清除', msg.message_id)
312
+ await this._clearSessionContext(chatId)
313
+ await this._sendMessage(chatId, '✅ 对话上下文已清除', msg.message_id)
312
314
  break
313
315
  case 'history':
314
- if (this._sessionPlugin) {
315
- const session = this._sessionPlugin.getSession(`telegram_${chatId}`)
316
- const sessions = this._sessionPlugin.listSessions().filter(s => s.id.startsWith('telegram_'))
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会话数:${sessions.length}\n历史消息:${session?.messages?.length || 0}\n最后活跃:${session?.lastActive?.toLocaleString() || '无'}`,
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 })
@@ -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
  */
@@ -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
- // DeepSeek thinking mode 模型不支持 temperature 参数
51
- if (isThinkingModel(this.model)) {
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 = isThinkingModel(this.model);
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 (isThinkingModel(this.model)) {
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 = isThinkingModel(this.model);
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 = config.providerOptions || {};
94
- // 使用常量替代硬编码数字
95
- this.providerOptions.maxOutputTokens = AGENT_CONFIG.MAX_OUTPUT_TOKENS;
96
- this.providerOptions.temperature = AGENT_CONFIG.TEMPERATURE;
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 = this._countMessagesTokens(messages);
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
- for (const [sessionId] of this._sessionContexts) {
169
- this.releaseSessionContext(sessionId, true);
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
- // 清理 Session Contexts
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
  }
@@ -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 = [toolRegisteredHandler, toolCallHandler, toolResultHandler, toolErrorHandler];
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
- const alreadyRegistered = agent._promptParts?.some(p => p.name === name);
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 listener of this._toolRegistryListeners) {
916
- this.toolRegistry.off('tool:registered', listener);
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
- if (!fs.existsSync(dir)) {
42
- fs.mkdirSync(dir, { recursive: true });
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
- if (!fs.existsSync(dir)) {
125
- fs.mkdirSync(dir, { recursive: true });
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
- fs.writeFileSync(filePath, content, 'utf-8');
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
- if (!fs.existsSync(filePath)) {
154
+ try {
155
+ await fsPromises.access(filePath);
156
+ } catch {
150
157
  return null;
151
158
  }
152
159
 
153
160
  try {
154
- const content = fs.readFileSync(filePath, 'utf-8');
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
- if (fs.existsSync(filePath)) {
179
- try {
180
- fs.unlinkSync(filePath);
181
- return true;
182
- } catch (err) {
183
- console.error(`[SessionStorage] Failed to delete ${sessionId}:`, err.message);
184
- return false;
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
- if (!fs.existsSync(dir)) {
215
+ try {
216
+ await fsPromises.access(dir);
217
+ } catch {
206
218
  return [];
207
219
  }
208
220
 
209
221
  try {
210
- const files = fs.readdirSync(dir);
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
  }