foliko 1.0.87 → 1.1.1

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.
Files changed (259) hide show
  1. package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
  2. package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
  3. package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
  4. package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
  5. package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
  6. package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
  7. package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
  8. package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  9. package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  10. package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  11. package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  12. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  13. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  14. package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  15. package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
  16. package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  17. package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  18. package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  19. package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  20. package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
  21. package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
  22. package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  23. package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  24. package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
  25. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  26. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
  27. package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
  28. package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
  29. package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
  30. package/.agent/ARCHITECTURE.md +288 -0
  31. package/.agent/agents/ambient-agent.md +57 -0
  32. package/.agent/agents/debugger.md +55 -0
  33. package/.agent/agents/email-assistant.md +49 -0
  34. package/.agent/agents/file-manager.md +42 -0
  35. package/.agent/agents/python-developer.md +60 -0
  36. package/.agent/agents/scheduler.md +59 -0
  37. package/.agent/agents/web-developer.md +45 -0
  38. package/.agent/data/default.json +325 -21
  39. package/.agent/data/plugins-state.json +194 -162
  40. package/.agent/data/puppeteer-sessions/undefined.json +6 -0
  41. package/.agent/mcp_config.json +0 -1
  42. package/.agent/mcp_config_updated.json +12 -0
  43. package/.agent/plugins/poster-plugin/README.md +304 -0
  44. package/.agent/plugins/poster-plugin/fonts/NotoColorEmoji-Regular.ttf +0 -0
  45. package/.agent/plugins/poster-plugin/fonts/PatuaOne-Regular.ttf +0 -0
  46. package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221.ttf +0 -0
  47. package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221/347/262/227/344/275/223.ttf +0 -0
  48. package/.agent/plugins/poster-plugin/index.js +13 -0
  49. package/.agent/plugins/poster-plugin/package.json +28 -0
  50. package/.agent/plugins/poster-plugin/src/canvas.js +161 -0
  51. package/.agent/plugins/poster-plugin/src/components/arrow.js +84 -0
  52. package/.agent/plugins/poster-plugin/src/components/avatar.js +71 -0
  53. package/.agent/plugins/poster-plugin/src/components/badge.js +85 -0
  54. package/.agent/plugins/poster-plugin/src/components/card.js +88 -0
  55. package/.agent/plugins/poster-plugin/src/components/chart.js +127 -0
  56. package/.agent/plugins/poster-plugin/src/components/chip.js +88 -0
  57. package/.agent/plugins/poster-plugin/src/components/columns.js +107 -0
  58. package/.agent/plugins/poster-plugin/src/components/cta.js +85 -0
  59. package/.agent/plugins/poster-plugin/src/components/divider.js +55 -0
  60. package/.agent/plugins/poster-plugin/src/components/feature.js +85 -0
  61. package/.agent/plugins/poster-plugin/src/components/featureGrid.js +112 -0
  62. package/.agent/plugins/poster-plugin/src/components/grid.js +118 -0
  63. package/.agent/plugins/poster-plugin/src/components/imageFrame.js +155 -0
  64. package/.agent/plugins/poster-plugin/src/components/index.js +62 -0
  65. package/.agent/plugins/poster-plugin/src/components/listItem.js +146 -0
  66. package/.agent/plugins/poster-plugin/src/components/notification.js +123 -0
  67. package/.agent/plugins/poster-plugin/src/components/progress.js +79 -0
  68. package/.agent/plugins/poster-plugin/src/components/progressCircle.js +117 -0
  69. package/.agent/plugins/poster-plugin/src/components/quote.js +97 -0
  70. package/.agent/plugins/poster-plugin/src/components/rating.js +85 -0
  71. package/.agent/plugins/poster-plugin/src/components/star.js +70 -0
  72. package/.agent/plugins/poster-plugin/src/components/statCard.js +105 -0
  73. package/.agent/plugins/poster-plugin/src/components/stepper.js +118 -0
  74. package/.agent/plugins/poster-plugin/src/components/table.js +159 -0
  75. package/.agent/plugins/poster-plugin/src/components/tagCloud.js +78 -0
  76. package/.agent/plugins/poster-plugin/src/components/timeline.js +105 -0
  77. package/.agent/plugins/poster-plugin/src/components/watermark.js +52 -0
  78. package/.agent/plugins/poster-plugin/src/composer.js +1904 -0
  79. package/.agent/plugins/poster-plugin/src/elements/artText.js +60 -0
  80. package/.agent/plugins/poster-plugin/src/elements/background.js +52 -0
  81. package/.agent/plugins/poster-plugin/src/elements/circle.js +31 -0
  82. package/.agent/plugins/poster-plugin/src/elements/image.js +71 -0
  83. package/.agent/plugins/poster-plugin/src/elements/index.js +26 -0
  84. package/.agent/plugins/poster-plugin/src/elements/line.js +23 -0
  85. package/.agent/plugins/poster-plugin/src/elements/polygon.js +63 -0
  86. package/.agent/plugins/poster-plugin/src/elements/rectangle.js +32 -0
  87. package/.agent/plugins/poster-plugin/src/elements/svg.js +92 -0
  88. package/.agent/plugins/poster-plugin/src/elements/text.js +107 -0
  89. package/.agent/plugins/poster-plugin/src/fonts.js +233 -0
  90. package/.agent/plugins/poster-plugin/src/index.js +1658 -0
  91. package/.agent/plugins/poster-plugin/src/presets.js +36 -0
  92. package/.agent/plugins/poster-plugin/src/templates/business.js +60 -0
  93. package/.agent/plugins/poster-plugin/src/templates/gradient.js +64 -0
  94. package/.agent/plugins/poster-plugin/src/templates/index.js +43 -0
  95. package/.agent/plugins/poster-plugin/src/templates/modern.js +69 -0
  96. package/.agent/plugins/poster-plugin/src/templates/simple.js +58 -0
  97. package/.agent/plugins/poster-plugin/src/templates/social.js +62 -0
  98. package/.agent/plugins/poster-plugin/src/templates/tech.js +84 -0
  99. package/.agent/plugins/{temp-repo/puppeteer-plugin → puppeteer-plugin}/index.js +1 -1
  100. package/.agent/plugins.json +5 -11
  101. package/.agent/rules/GEMINI.md +273 -0
  102. package/.agent/rules/allow-rule.md +77 -0
  103. package/.agent/rules/log-rule.md +83 -0
  104. package/.agent/rules/security-rule.md +93 -0
  105. package/.agent/scripts/auto_preview.py +148 -0
  106. package/.agent/scripts/checklist.py +217 -0
  107. package/.agent/scripts/session_manager.py +120 -0
  108. package/.agent/scripts/verify_all.py +327 -0
  109. package/.agent/sessions/cli_default.json +419 -0
  110. package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +2195 -0
  111. package/.agent/skills/api-patterns/SKILL.md +81 -0
  112. package/.agent/skills/api-patterns/api-style.md +42 -0
  113. package/.agent/skills/api-patterns/auth.md +24 -0
  114. package/.agent/skills/api-patterns/documentation.md +26 -0
  115. package/.agent/skills/api-patterns/graphql.md +41 -0
  116. package/.agent/skills/api-patterns/rate-limiting.md +31 -0
  117. package/.agent/skills/api-patterns/response.md +37 -0
  118. package/.agent/skills/api-patterns/rest.md +40 -0
  119. package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
  120. package/.agent/skills/api-patterns/security-testing.md +122 -0
  121. package/.agent/skills/api-patterns/trpc.md +41 -0
  122. package/.agent/skills/api-patterns/versioning.md +22 -0
  123. package/.agent/skills/app-builder/SKILL.md +75 -0
  124. package/.agent/skills/app-builder/agent-coordination.md +71 -0
  125. package/.agent/skills/app-builder/feature-building.md +53 -0
  126. package/.agent/skills/app-builder/project-detection.md +34 -0
  127. package/.agent/skills/app-builder/scaffolding.md +118 -0
  128. package/.agent/skills/app-builder/tech-stack.md +40 -0
  129. package/.agent/skills/app-builder/templates/SKILL.md +39 -0
  130. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
  131. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
  132. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
  133. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
  134. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
  135. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
  136. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
  137. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
  138. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
  139. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
  140. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
  141. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
  142. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
  143. package/.agent/skills/architecture/SKILL.md +55 -0
  144. package/.agent/skills/architecture/context-discovery.md +43 -0
  145. package/.agent/skills/architecture/examples.md +94 -0
  146. package/.agent/skills/architecture/pattern-selection.md +68 -0
  147. package/.agent/skills/architecture/patterns-reference.md +50 -0
  148. package/.agent/skills/architecture/trade-off-analysis.md +77 -0
  149. package/.agent/skills/clean-code/SKILL.md +201 -0
  150. package/.agent/skills/doc.md +177 -0
  151. package/.agent/skills/frontend-design/SKILL.md +418 -0
  152. package/.agent/skills/frontend-design/animation-guide.md +331 -0
  153. package/.agent/skills/frontend-design/color-system.md +311 -0
  154. package/.agent/skills/frontend-design/decision-trees.md +418 -0
  155. package/.agent/skills/frontend-design/motion-graphics.md +306 -0
  156. package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
  157. package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
  158. package/.agent/skills/frontend-design/typography-system.md +345 -0
  159. package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
  160. package/.agent/skills/frontend-design/visual-effects.md +383 -0
  161. package/.agent/skills/i18n-localization/SKILL.md +154 -0
  162. package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
  163. package/.agent/skills/mcp-builder/SKILL.md +176 -0
  164. package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
  165. package/.agent/workflows/brainstorm.md +113 -0
  166. package/.agent/workflows/create.md +59 -0
  167. package/.agent/workflows/debug.md +103 -0
  168. package/.agent/workflows/deploy.md +176 -0
  169. package/.agent/workflows/enhance.md +63 -0
  170. package/.agent/workflows/orchestrate.md +237 -0
  171. package/.agent/workflows/plan.md +89 -0
  172. package/.agent/workflows/preview.md +81 -0
  173. package/.agent/workflows/simple-test.md +42 -0
  174. package/.agent/workflows/status.md +86 -0
  175. package/.agent/workflows/structured-orchestrate.md +180 -0
  176. package/.agent/workflows/test.md +144 -0
  177. package/.agent/workflows/ui-ux-pro-max.md +296 -0
  178. package/.claude/settings.local.json +20 -8
  179. package/.env.example +56 -56
  180. package/CLAUDE.md +144 -108
  181. package/README.md +441 -441
  182. package/calc_tokens_weixin.js +81 -0
  183. package/cli/src/commands/chat.js +2 -1
  184. package/docs/CONTEXT_DESIGN.md +1596 -0
  185. package/examples/test-concurrent-chat.js +60 -60
  186. package/foliko-creative-3.png +0 -0
  187. package/foliko-creative-4.png +0 -0
  188. package/foliko-creative-5.png +0 -0
  189. package/package.json +2 -2
  190. package/plugins/default-plugins.js +2 -1
  191. package/plugins/extension-executor-plugin.js +91 -2
  192. package/plugins/file-system-plugin.js +2 -2
  193. package/plugins/memory-plugin.js +984 -0
  194. package/plugins/session-plugin.js +57 -1
  195. package/plugins/weixin-plugin.js +33 -23
  196. package/skills/find-skills/AGENTS.md +162 -162
  197. package/skills/find-skills/SKILL.md +133 -133
  198. package/skills/poster-guide/SKILL.md +1059 -0
  199. package/skills/python-plugin-dev/SKILL.md +238 -238
  200. package/skills/skill-guide/SKILL.md +130 -108
  201. package/src/capabilities/skill-manager.js +99 -0
  202. package/src/core/agent-chat.js +620 -141
  203. package/src/core/agent-context.js +188 -0
  204. package/src/core/agent.js +6 -2
  205. package/src/core/context-manager.js +283 -0
  206. package/src/core/framework.js +264 -3
  207. package/src/core/plugin-manager.js +79 -2
  208. package/src/core/request-context.js +98 -0
  209. package/src/core/session-context.js +341 -0
  210. package/src/core/session-storage.js +274 -0
  211. package/src/executors/mcp-executor.js +2 -2
  212. package/src/utils/index.js +239 -67
  213. package/src/utils/plugin-helpers.js +17 -0
  214. package/story-cover-book-v2.png +0 -0
  215. package/story-cover-japanese-1.png +0 -0
  216. package/story-cover-japanese-2.png +0 -0
  217. package/story-cover-japanese-3.png +0 -0
  218. package/story-cover-moran.png +0 -0
  219. package/undefined.png +0 -0
  220. package//346/265/267/346/212/245/346/217/222/344/273/266.md +621 -0
  221. package/.agent/agents/code-assistant.json +0 -14
  222. package/.agent/agents/email-assistant.json +0 -14
  223. package/.agent/agents/file-assistant.json +0 -15
  224. package/.agent/agents/system-assistant.json +0 -15
  225. package/.agent/agents/web-assistant.json +0 -12
  226. package/.agent/data/ambient/goals.json +0 -50
  227. package/.agent/data/ambient/memories.json +0 -7
  228. package/.agent/data/scheduler/tasks.json +0 -1
  229. package/.agent/package.json +0 -8
  230. package/.agent/plugins/__pycache__/test_plugin.cpython-312.pyc +0 -0
  231. package/.agent/plugins/daytona/README.md +0 -89
  232. package/.agent/plugins/daytona/index.js +0 -377
  233. package/.agent/plugins/daytona/package.json +0 -12
  234. package/.agent/plugins/marknative/README.md +0 -134
  235. package/.agent/plugins/marknative/index.js +0 -228
  236. package/.agent/plugins/marknative/package.json +0 -12
  237. package/.agent/plugins/marknative/update-readme.js +0 -134
  238. package/.agent/plugins/system-info/index.js +0 -387
  239. package/.agent/plugins/system-info/package.json +0 -4
  240. package/.agent/plugins/system-info/test.js +0 -40
  241. package/.agent/plugins/temp-repo/LICENSE +0 -201
  242. package/.agent/plugins/test_plugin.py +0 -304
  243. package/.agent/python-scripts/test_sample.py +0 -24
  244. package/.agent/skills/agent-browser/SKILL.md +0 -311
  245. package/.agent/skills/agent-browser/TEST_PLAN.md +0 -200
  246. package/.agent/skills/sysinfo/SKILL.md +0 -38
  247. package/.agent/skills/sysinfo/system-info.sh +0 -130
  248. package/.agent/skills/workflow/SKILL.md +0 -324
  249. package/.agent/workflows/email-digest.json +0 -50
  250. package/.agent/workflows/file-backup.json +0 -21
  251. package/.agent/workflows/get-ip-notify.json +0 -32
  252. package/.agent/workflows/news-aggregator.json +0 -93
  253. package/.agent/workflows/news-dashboard-v2.json +0 -94
  254. package/.agent/workflows/notification-batch.json +0 -32
  255. package/examples/test-chat-debug.js +0 -102
  256. package/examples/test-chat-result.js +0 -76
  257. package/examples/test-chat-stream-diff.js +0 -63
  258. /package/.agent/plugins/{temp-repo/puppeteer-plugin → puppeteer-plugin}/README.md +0 -0
  259. /package/.agent/plugins/{temp-repo/puppeteer-plugin → puppeteer-plugin}/package.json +0 -0
@@ -7,6 +7,23 @@ const { EventEmitter } = require('../utils/event-emitter');
7
7
  const { logger } = require('../utils/logger');
8
8
  const { generateText, stepCountIs } = require('ai');
9
9
  const { prepareMessagesForAPI } = require('../utils');
10
+ const fs = require('fs/promises');
11
+
12
+ /**
13
+ * 从消息中提取 toolCallId(兼容两种格式)
14
+ * 格式1: msg.toolCallId (旧格式)
15
+ * 格式2: msg.content[0].toolCallId (AI SDK CoreMessage 格式)
16
+ * @param {Object} msg - 消息对象
17
+ * @returns {string|null}
18
+ */
19
+ function extractToolCallId(msg) {
20
+ if (msg.toolCallId) return msg.toolCallId;
21
+ if (Array.isArray(msg.content) && msg.content.length > 0) {
22
+ const firstContent = msg.content[0];
23
+ if (firstContent && firstContent.toolCallId) return firstContent.toolCallId;
24
+ }
25
+ return null;
26
+ }
10
27
 
11
28
  // 模型上下文限制表(留 15-20% 余量给 system prompt 和输出)
12
29
  const MODEL_CONTEXT_LIMITS = {
@@ -14,7 +31,7 @@ const MODEL_CONTEXT_LIMITS = {
14
31
  'deepseek-chat': 28000,
15
32
  'deepseek-coder': 28000,
16
33
  // MiniMax
17
- 'MiniMax-M2.7': 90000,
34
+ 'MiniMax-M2.7': 130800,
18
35
  // OpenAI
19
36
  'gpt-4': 100000,
20
37
  'gpt-4o': 100000,
@@ -76,7 +93,7 @@ class SimpleTokenizer {
76
93
  const _globalTokenizer = new SimpleTokenizer();
77
94
 
78
95
  // 压缩超时时间(毫秒)
79
- const COMPRESSION_TIMEOUT = 30000;
96
+ const COMPRESSION_TIMEOUT = 120000; // 2分钟
80
97
 
81
98
  class AgentChatHandler extends EventEmitter {
82
99
  /**
@@ -96,7 +113,8 @@ class AgentChatHandler extends EventEmitter {
96
113
  this.providerOptions = config.providerOptions || {};
97
114
 
98
115
  this._systemPrompt = config.systemPrompt || 'You are a helpful assistant.';
99
- this._messages = [];
116
+ // this._messages = []; // 移除共享消息存储!改用 Per-Session
117
+ this._sessionMessageStores = new Map(); // sessionId → { messages: [], historyLoaded: false, compressionState: {} }
100
118
  this._tools = new Map();
101
119
  this._maxSteps = 20; // 降低默认步骤数,减少上下文消耗
102
120
 
@@ -127,10 +145,58 @@ class AgentChatHandler extends EventEmitter {
127
145
  // Per-Session 队列管理
128
146
  this._sessionQueues = new Map(); // sessionId -> Queue of {message, options, resolve, reject}
129
147
  this._processingSessions = new Set(); // sessionId -> processing flag
148
+
149
+ // Session Memory prepareStep(从 memory 插件获取)
150
+ this._sessionMemoryPrepareStep = null;
151
+ if (agent?.framework) {
152
+ const memoryPlugin = agent.framework.pluginManager.get('memory');
153
+ if (memoryPlugin?.instance?.getPrepareStep) {
154
+ this._sessionMemoryPrepareStep = memoryPlugin.instance.getPrepareStep();
155
+ logger.debug('Session memory prepareStep loaded');
156
+ }
157
+ }
158
+ }
159
+
160
+ /**
161
+ * 获取 Per-Session 的消息存储
162
+ * @param {string} sessionId - 会话 ID
163
+ * @returns {Object} 消息存储对象
164
+ * @private
165
+ */
166
+ _getSessionMessageStore(sessionId) {
167
+ if (!sessionId) {
168
+ // 无 session 的单次请求,使用临时存储
169
+ return { messages: [], historyLoaded: false, compressionState: {}, save: () => {} };
170
+ }
171
+
172
+ if (!this._sessionMessageStores.has(sessionId)) {
173
+ // 自动从 session 加载消息
174
+ const savedMessages = this._loadHistoryFromSession(sessionId);
175
+ this._sessionMessageStores.set(sessionId, {
176
+ sessionId, // 保存 sessionId 引用
177
+ messages: savedMessages,
178
+ historyLoaded: true, // 已加载
179
+ compressionState: {
180
+ lastCompressedAt: null,
181
+ lastTokenCount: 0,
182
+ count: 0,
183
+ },
184
+ save: () => {
185
+ // 简洁的保存方法
186
+ if (this.agent?.framework) {
187
+ const sessionCtx = this.agent.framework.getOrCreateSessionContext(sessionId);
188
+ if (sessionCtx) {
189
+ sessionCtx.replaceMessages(this._sessionMessageStores.get(sessionId).messages);
190
+ }
191
+ }
192
+ },
193
+ });
194
+ }
195
+ return this._sessionMessageStores.get(sessionId);
130
196
  }
131
197
 
132
198
  /**
133
- * 从 session 存储加载聊天历史
199
+ * 从 SessionContext 加载聊天历史
134
200
  * @param {string} sessionId - 会话 ID
135
201
  * @returns {Array} 消息数组
136
202
  * @private
@@ -139,11 +205,10 @@ class AgentChatHandler extends EventEmitter {
139
205
  if (!sessionId || !this.agent?.framework) return [];
140
206
 
141
207
  try {
142
- const sessionPlugin = this.agent.framework.pluginManager.get('session');
143
- if (!sessionPlugin) return [];
208
+ const sessionCtx = this.agent.framework.getOrCreateSessionContext(sessionId);
209
+ if (!sessionCtx) return [];
144
210
 
145
- const messages = sessionPlugin.getHistory(sessionId);
146
- // 直接返回,保存时已清理,数据应该是干净的
211
+ const messages = sessionCtx.getMessages();
147
212
  return messages || [];
148
213
  } catch (err) {
149
214
  // 忽略加载错误
@@ -152,7 +217,7 @@ class AgentChatHandler extends EventEmitter {
152
217
  }
153
218
 
154
219
  /**
155
- * 保存聊天历史到 session 存储
220
+ * 保存聊天历史到 SessionContext
156
221
  * @param {string} sessionId - 会话 ID
157
222
  * @param {Array} messages - 消息数组
158
223
  * @private
@@ -161,12 +226,12 @@ class AgentChatHandler extends EventEmitter {
161
226
  if (!sessionId || !this.agent?.framework || !messages) return;
162
227
 
163
228
  try {
164
- const sessionPlugin = this.agent.framework.pluginManager.get('session');
165
- if (!sessionPlugin) return;
229
+ const sessionCtx = this.agent.framework.getOrCreateSessionContext(sessionId);
230
+ if (!sessionCtx) return;
166
231
 
167
232
  // 清理消息格式后整体替换
168
233
  const cleanedMessages = prepareMessagesForAPI(messages);
169
- sessionPlugin.replaceMessages(sessionId, cleanedMessages);
234
+ sessionCtx.replaceMessages(cleanedMessages);
170
235
  } catch (err) {
171
236
  // 忽略保存错误
172
237
  }
@@ -269,9 +334,48 @@ class AgentChatHandler extends EventEmitter {
269
334
  total += this._countTokens(msg.content);
270
335
  } else if (Array.isArray(msg.content)) {
271
336
  for (const part of msg.content) {
272
- if (part && typeof part === 'object' && part.text) {
337
+ if (!part || typeof part !== 'object') continue;
338
+
339
+ // 处理普通文本
340
+ if (part.text) {
273
341
  total += this._countTokens(String(part.text));
274
342
  }
343
+
344
+ // 处理 tool-result 的 output 字段
345
+ if (part.type === 'tool-result' && part.output) {
346
+ if (typeof part.output === 'string') {
347
+ total += this._countTokens(part.output);
348
+ } else if (part.output && typeof part.output === 'object') {
349
+ // 处理 { type: 'text', value: '...' } 或 { type: 'text', html: '...' } 等
350
+ if (part.output.value) {
351
+ total += this._countTokens(String(part.output.value));
352
+ }
353
+ if (part.output.html) {
354
+ total += this._countTokens(part.output.html);
355
+ }
356
+ // 其他字段也序列化计算
357
+ try {
358
+ const others = {};
359
+ for (const [k, v] of Object.entries(part.output)) {
360
+ if (k !== 'value' && k !== 'html') others[k] = v;
361
+ }
362
+ if (Object.keys(others).length > 0) {
363
+ total += this._countTokens(JSON.stringify(others));
364
+ }
365
+ } catch (e) {
366
+ // 忽略
367
+ }
368
+ }
369
+ }
370
+
371
+ // 处理 tool-use 的 input 字段
372
+ if (part.type === 'tool_use' && part.input) {
373
+ try {
374
+ total += this._countTokens(JSON.stringify(part.input));
375
+ } catch (e) {
376
+ // 忽略
377
+ }
378
+ }
275
379
  }
276
380
  } else if (msg.content && typeof msg.content === 'object') {
277
381
  // 处理工具结果等对象类型
@@ -287,16 +391,6 @@ class AgentChatHandler extends EventEmitter {
287
391
  return total;
288
392
  }
289
393
 
290
- /**
291
- * 检查是否需要压缩上下文
292
- * @returns {boolean}
293
- * @private
294
- */
295
- _shouldCompress() {
296
- const totalTokens = this._countMessagesTokens(this._messages);
297
- return totalTokens > this._maxContextTokens * this._compressionThreshold;
298
- }
299
-
300
394
  /**
301
395
  * 计算工具定义的 token 数(估算)
302
396
  * @returns {number}
@@ -321,21 +415,6 @@ class AgentChatHandler extends EventEmitter {
321
415
  return total;
322
416
  }
323
417
 
324
- /**
325
- * 检查是否需要压缩(包括工具定义)
326
- * @returns {boolean}
327
- * @private
328
- */
329
- _shouldCompressWithTools() {
330
- const messagesTokens = this._countMessagesTokens(this._messages);
331
- const toolsTokens = this._countToolsTokens();
332
- const systemPromptTokens = this._countTokens(this._systemPrompt);
333
- const total = messagesTokens + toolsTokens + systemPromptTokens;
334
-
335
- // 如果总token数超过上下文限制的 85%,就压缩
336
- return total > this._maxContextTokens * 0.85;
337
- }
338
-
339
418
  /**
340
419
  * 压缩上下文消息(智能摘要模式,支持超时控制)
341
420
  * 策略:
@@ -343,23 +422,29 @@ class AgentChatHandler extends EventEmitter {
343
422
  * 2. 否则使用简单裁剪 + 标记
344
423
  * 3. 支持超时控制,防止压缩阻塞太久
345
424
  * @param {string} sessionId - 会话 ID(用于压缩后同步)
425
+ * @param {Array} messages - 消息数组引用
426
+ * @param {Object} messageStore - 消息存储对象
346
427
  * @private
347
428
  */
348
- async _compressContext(sessionId) {
429
+ async _compressContext(sessionId, messages, messageStore) {
349
430
  // 如果已经有压缩在进行,返回现有的 Promise
350
431
  if (this._compressionInProgress && this._compressionPromise) {
351
432
  logger.debug('Compression already in progress, waiting for it...');
352
433
  return this._compressionPromise;
353
434
  }
354
435
 
355
- if (this._messages.length <= this._keepRecentMessages) {
436
+ if (messages.length <= this._keepRecentMessages) {
356
437
  return;
357
438
  }
358
439
 
359
440
  this._compressionInProgress = true;
360
441
 
361
442
  // 创建压缩 Promise,带超时控制
362
- this._compressionPromise = this._executeCompressionWithTimeout(sessionId).finally(() => {
443
+ this._compressionPromise = this._executeCompressionWithTimeout(
444
+ sessionId,
445
+ messages,
446
+ messageStore
447
+ ).finally(() => {
363
448
  this._compressionInProgress = false;
364
449
  this._compressionPromise = null;
365
450
  });
@@ -370,15 +455,20 @@ class AgentChatHandler extends EventEmitter {
370
455
  /**
371
456
  * 执行压缩(带超时)
372
457
  * @param {string} sessionId - 会话 ID
458
+ * @param {Array} messages - 消息数组引用
459
+ * @param {Object} messageStore - 消息存储对象
373
460
  * @private
374
461
  */
375
- async _executeCompressionWithTimeout(sessionId) {
462
+ async _executeCompressionWithTimeout(sessionId, messages, messageStore) {
376
463
  try {
377
- return await Promise.race([this._doCompress(sessionId), this._createTimeoutPromise()]);
464
+ return await Promise.race([
465
+ this._doCompress(sessionId, messages, messageStore),
466
+ this._createTimeoutPromise(),
467
+ ]);
378
468
  } catch (err) {
379
469
  logger.warn('Compression failed:', err.message);
380
470
  // 压缩失败时使用简单的截断策略
381
- this._simpleCompress(sessionId);
471
+ this._simpleCompress(sessionId, messages, messageStore);
382
472
  }
383
473
  }
384
474
 
@@ -416,11 +506,13 @@ class AgentChatHandler extends EventEmitter {
416
506
  /**
417
507
  * 执行实际压缩逻辑
418
508
  * @param {string} sessionId - 会话 ID
509
+ * @param {Array} messages - 消息数组引用
510
+ * @param {Object} messageStore - 消息存储对象
419
511
  * @private
420
512
  */
421
- async _doCompress(sessionId) {
422
- const systemMessages = this._messages.filter((m) => m.role === 'system');
423
- const otherMessages = this._messages.filter((m) => m.role !== 'system');
513
+ async _doCompress(sessionId, messages, messageStore) {
514
+ const systemMessages = messages.filter((m) => m.role === 'system');
515
+ const otherMessages = messages.filter((m) => m.role !== 'system');
424
516
 
425
517
  // 保留最近的 N 条非系统消息
426
518
  const recentMessages = otherMessages.slice(-this._keepRecentMessages);
@@ -448,29 +540,120 @@ class AgentChatHandler extends EventEmitter {
448
540
  content: summaryContent,
449
541
  };
450
542
 
451
- this._messages = [...systemMessages, summary, ...recentMessages].filter(
452
- (item) => item.role !== 'tool'
453
- );
543
+ // 构建保留的消息,确保 tool call 和 tool result 配对不被分离
544
+ const assistantToolCalls = new Map(); // toolCallId -> assistant message index
545
+ const toolResults = new Map(); // toolCallId -> tool result indices
546
+
547
+ // 第一遍:找出所有 tool call 和它们的 results
548
+ for (let i = 0; i < recentMessages.length; i++) {
549
+ const msg = recentMessages[i];
550
+ if (msg.role === 'assistant' && msg.content) {
551
+ const content = Array.isArray(msg.content) ? msg.content : [msg.content];
552
+ for (const item of content) {
553
+ if (item.type === 'tool-call' && item.toolCallId) {
554
+ assistantToolCalls.set(item.toolCallId, i);
555
+ }
556
+ }
557
+ }
558
+ if (msg.role === 'tool' && Array.isArray(msg.content)) {
559
+ // 遍历 tool message 中的所有 tool-results
560
+ for (const item of msg.content) {
561
+ if (item && item.type === 'tool-result' && item.toolCallId) {
562
+ if (!toolResults.has(item.toolCallId)) {
563
+ toolResults.set(item.toolCallId, []);
564
+ }
565
+ toolResults.get(item.toolCallId).push(i);
566
+ }
567
+ }
568
+ }
569
+ }
570
+
571
+ // 第二遍:找出哪些 tool call 没有对应的 result(需要保留)
572
+ const orphanToolCalls = new Set();
573
+ for (const [toolCallId, assistantIdx] of assistantToolCalls) {
574
+ if (!toolResults.has(toolCallId)) {
575
+ orphanToolCalls.add(toolCallId);
576
+ }
577
+ }
578
+
579
+ // 过滤时保留:有 result 的 tool call,以及没有 result 的 tool call(但要附带其 result)
580
+ const indicesToKeep = new Set();
581
+ for (let i = 0; i < recentMessages.length; i++) {
582
+ const msg = recentMessages[i];
583
+ if (msg.role === 'tool') {
584
+ // 保留所有 tool result(它们是必要的)
585
+ indicesToKeep.add(i);
586
+ } else if (msg.role === 'assistant') {
587
+ // 检查这个 assistant 消息是否有 tool call
588
+ let hasToolCall = false;
589
+ if (msg.content) {
590
+ const content = Array.isArray(msg.content) ? msg.content : [msg.content];
591
+ for (const item of content) {
592
+ if (item.type === 'tool-call' && item.toolCallId) {
593
+ hasToolCall = true;
594
+ break;
595
+ }
596
+ }
597
+ }
598
+ if (hasToolCall) {
599
+ indicesToKeep.add(i);
600
+ } else if (i >= recentMessages.length - 3) {
601
+ // 保留最近几条 assistant 消息(它们可能包含重要上下文)
602
+ indicesToKeep.add(i);
603
+ }
604
+ } else {
605
+ // user, system 等角色默认保留
606
+ indicesToKeep.add(i);
607
+ }
608
+ }
609
+
610
+ // 如果有孤儿的 tool call(没有 result),保留该 assistant 消息
611
+ for (const [toolCallId, assistantIdx] of assistantToolCalls) {
612
+ if (!toolResults.has(toolCallId)) {
613
+ indicesToKeep.add(assistantIdx);
614
+ }
615
+ }
616
+
617
+ // 按索引排序并重建消息数组
618
+ const sortedIndices = Array.from(indicesToKeep).sort((a, b) => a - b);
619
+ const filteredRecentMessages = sortedIndices.map((i) => recentMessages[i]);
620
+
621
+ // 清理孤立 tool results(tool call 已被总结,但 result 还在)
622
+ this._cleanOrphanedToolResults(filteredRecentMessages);
623
+
624
+ // 直接修改传入的 messages 数组
625
+ messages.length = 0;
626
+ messages.push(...systemMessages, summary, ...filteredRecentMessages);
627
+
628
+ // 更新压缩状态
454
629
  this._compressionCount++;
455
- const totalTokens = this._countMessagesTokens(this._messages);
630
+ if (messageStore.compressionState) {
631
+ messageStore.compressionState.count++;
632
+ messageStore.compressionState.lastCompressedAt = Date.now();
633
+ messageStore.compressionState.lastTokenCount = this._countMessagesTokens(messages);
634
+ }
635
+
636
+ const totalTokens = this._countMessagesTokens(messages);
456
637
  logger.info(
457
- `Context compressed (${this._compressionCount} times). Messages: ${this._messages.length}, Est. tokens: ${totalTokens}`
638
+ `Context compressed (${this._compressionCount} times). Messages: ${messages.length}, Est. tokens: ${totalTokens}`
458
639
  );
459
640
 
460
641
  // 压缩后立即同步到 session
461
642
  if (sessionId) {
462
- this._saveHistoryToSession(sessionId, this._messages);
643
+ this._saveHistoryToSession(sessionId, messages);
463
644
  }
464
645
  }
465
646
 
466
647
  /**
467
648
  * 简单压缩(当 AI 压缩失败时使用)
468
649
  * @param {string} sessionId - 会话 ID
650
+ * @param {Array} messages - 消息数组引用
651
+ * @param {Object} messageStore - 消息存储对象
469
652
  * @private
470
653
  */
471
- _simpleCompress(sessionId) {
472
- const systemMessages = this._messages.filter((m) => m.role === 'system');
473
- const otherMessages = this._messages.filter((m) => m.role !== 'system');
654
+ _simpleCompress(sessionId, messages, messageStore) {
655
+ const systemMessages = messages.filter((m) => m.role === 'system');
656
+ const otherMessages = messages.filter((m) => m.role !== 'system');
474
657
  const recentMessages = otherMessages.slice(-this._keepRecentMessages);
475
658
  const compressedCount = otherMessages.length - this._keepRecentMessages;
476
659
 
@@ -481,18 +664,137 @@ class AgentChatHandler extends EventEmitter {
481
664
  content: summaryContent,
482
665
  };
483
666
 
484
- this._messages = [...systemMessages, summary, ...recentMessages].filter(
485
- (item) => item.role !== 'tool'
486
- );
667
+ // 构建保留的消息,确保 tool call 和 tool result 配对不被分离
668
+ const assistantToolCalls = new Map(); // toolCallId -> assistant message index
669
+ const toolResults = new Map(); // toolCallId -> tool result indices
670
+
671
+ // 第一遍:找出所有 tool call 和它们的 results
672
+ for (let i = 0; i < recentMessages.length; i++) {
673
+ const msg = recentMessages[i];
674
+ if (msg.role === 'assistant' && msg.content) {
675
+ const content = Array.isArray(msg.content) ? msg.content : [msg.content];
676
+ for (const item of content) {
677
+ if (item.type === 'tool-call' && item.toolCallId) {
678
+ assistantToolCalls.set(item.toolCallId, i);
679
+ }
680
+ }
681
+ }
682
+ if (msg.role === 'tool' && Array.isArray(msg.content)) {
683
+ // 遍历 tool message 中的所有 tool-results
684
+ for (const item of msg.content) {
685
+ if (item && item.type === 'tool-result' && item.toolCallId) {
686
+ if (!toolResults.has(item.toolCallId)) {
687
+ toolResults.set(item.toolCallId, []);
688
+ }
689
+ toolResults.get(item.toolCallId).push(i);
690
+ }
691
+ }
692
+ }
693
+ }
694
+
695
+ // 第二遍:找出哪些 tool call 没有对应的 result(需要保留)
696
+ const orphanToolCalls = new Set();
697
+ for (const [toolCallId, assistantIdx] of assistantToolCalls) {
698
+ if (!toolResults.has(toolCallId)) {
699
+ orphanToolCalls.add(toolCallId);
700
+ }
701
+ }
702
+
703
+ // 过滤时保留:有 result 的 tool call,以及没有 result 的 tool call
704
+ const indicesToKeep = new Set();
705
+ for (let i = 0; i < recentMessages.length; i++) {
706
+ const msg = recentMessages[i];
707
+ if (msg.role === 'tool') {
708
+ // 保留所有 tool result
709
+ indicesToKeep.add(i);
710
+ } else if (msg.role === 'assistant') {
711
+ let hasToolCall = false;
712
+ if (msg.content) {
713
+ const content = Array.isArray(msg.content) ? msg.content : [msg.content];
714
+ for (const item of content) {
715
+ if (item.type === 'tool-call' && item.toolCallId) {
716
+ hasToolCall = true;
717
+ break;
718
+ }
719
+ }
720
+ }
721
+ if (hasToolCall) {
722
+ indicesToKeep.add(i);
723
+ } else if (i >= recentMessages.length - 3) {
724
+ // 保留最近几条 assistant 消息
725
+ indicesToKeep.add(i);
726
+ }
727
+ } else {
728
+ // user, system 等角色默认保留
729
+ indicesToKeep.add(i);
730
+ }
731
+ }
732
+
733
+ // 如果有孤儿的 tool call(没有 result),保留该 assistant 消息
734
+ for (const [toolCallId, assistantIdx] of assistantToolCalls) {
735
+ if (!toolResults.has(toolCallId)) {
736
+ indicesToKeep.add(assistantIdx);
737
+ }
738
+ }
739
+
740
+ // 按索引排序并重建消息数组
741
+ const sortedIndices = Array.from(indicesToKeep).sort((a, b) => a - b);
742
+ const filteredRecentMessages = sortedIndices.map((i) => recentMessages[i]);
743
+
744
+ // 清理孤立 tool results(tool call 已被总结,但 result 还在)
745
+ this._cleanOrphanedToolResults(filteredRecentMessages);
746
+
747
+ // 直接修改传入的 messages 数组
748
+ messages.length = 0;
749
+ messages.push(...systemMessages, summary, ...filteredRecentMessages);
750
+
751
+ // 更新压缩状态
487
752
  this._compressionCount++;
753
+ if (messageStore.compressionState) {
754
+ messageStore.compressionState.count++;
755
+ messageStore.compressionState.lastCompressedAt = Date.now();
756
+ messageStore.compressionState.lastTokenCount = this._countMessagesTokens(messages);
757
+ }
488
758
 
489
759
  logger.info(
490
- `Context simple compressed (${this._compressionCount} times). Messages: ${this._messages.length}`
760
+ `Context simple compressed (${this._compressionCount} times). Messages: ${messages.length}`
491
761
  );
492
762
 
493
763
  // 压缩后立即同步到 session
494
764
  if (sessionId) {
495
- this._saveHistoryToSession(sessionId, this._messages);
765
+ this._saveHistoryToSession(sessionId, messages);
766
+ }
767
+ }
768
+
769
+ /**
770
+ * 清理孤立的 tool results(对应的 tool call 已被压缩删除)
771
+ * @param {Array} messages - 消息数组
772
+ * @private
773
+ */
774
+ _cleanOrphanedToolResults(messages) {
775
+ // 构建当前消息中存在的 toolCallId 集合
776
+ const validToolCallIds = new Set();
777
+ for (const msg of messages) {
778
+ if (msg.role === 'assistant' && msg.content) {
779
+ const content = Array.isArray(msg.content) ? msg.content : [msg.content];
780
+ for (const item of content) {
781
+ if (item.type === 'tool-call' && item.toolCallId) {
782
+ validToolCallIds.add(item.toolCallId);
783
+ }
784
+ }
785
+ }
786
+ }
787
+
788
+ // 过滤掉孤立 tool results
789
+ for (const msg of messages) {
790
+ if (msg.role === 'tool' && Array.isArray(msg.content)) {
791
+ msg.content = msg.content.filter((item) => {
792
+ if (item && item.type === 'tool-result' && item.toolCallId) {
793
+ return validToolCallIds.has(item.toolCallId);
794
+ }
795
+ return true;
796
+ });
797
+ }
496
798
  }
497
799
  }
498
800
 
@@ -677,23 +979,34 @@ ${truncatedContent}${truncatedNote}
677
979
  * @param {string} sessionId - 可选,指定 session 则同时清除 session 中的历史
678
980
  */
679
981
  clearHistory(sessionId) {
680
- this._messages = [];
681
- this._compressionCount = 0;
682
-
683
- // 同时清除 session 中的历史
684
- if (sessionId && this.agent?.framework) {
685
- try {
686
- const sessionPlugin = this.agent.framework.pluginManager.get('session');
687
- if (sessionPlugin) {
688
- const session = sessionPlugin.getSession(sessionId);
689
- if (session) {
690
- session.messages = [];
982
+ if (sessionId) {
983
+ // 清除 Per-Session 消息存储
984
+ const messageStore = this._sessionMessageStores.get(sessionId);
985
+ if (messageStore) {
986
+ messageStore.messages = [];
987
+ messageStore.historyLoaded = false;
988
+ messageStore.compressionState = {
989
+ lastCompressedAt: null,
990
+ lastTokenCount: 0,
991
+ count: 0,
992
+ };
993
+ }
994
+ // 清除 SessionContext 中的历史
995
+ if (this.agent?.framework) {
996
+ try {
997
+ const sessionCtx = this.agent.framework.getSessionContext(sessionId);
998
+ if (sessionCtx) {
999
+ sessionCtx.clearMessages();
691
1000
  }
1001
+ } catch (err) {
1002
+ // 忽略错误
692
1003
  }
693
- } catch (err) {
694
- // 忽略错误
695
1004
  }
1005
+ } else {
1006
+ // 清除所有 Per-Session 消息存储
1007
+ this._sessionMessageStores.clear();
696
1008
  }
1009
+ this._compressionCount = 0;
697
1010
  return this;
698
1011
  }
699
1012
 
@@ -739,19 +1052,13 @@ ${truncatedContent}${truncatedNote}
739
1052
  */
740
1053
  async _doChat(message, options = {}) {
741
1054
  const sessionId = options.sessionId || null;
742
- const context = { sessionId, isStream: false };
743
1055
  const framework = this.agent.framework;
744
1056
 
745
- try {
746
- // session 加载聊天历史
747
- if (sessionId) {
748
- const savedMessages = this._loadHistoryFromSession(sessionId);
749
- if (savedMessages.length > 0) {
750
- this._messages = savedMessages;
751
- logger.info(`Loaded ${savedMessages.length} messages from session ${sessionId}`);
752
- }
753
- }
1057
+ // 获取 Per-Session 的消息存储(自动从 session 加载)
1058
+ const messageStore = this._getSessionMessageStore(sessionId);
1059
+ const messages = messageStore.messages;
754
1060
 
1061
+ try {
755
1062
  // 关键:每次 chat 调用时刷新系统提示词,确保包含最新的工具/技能描述
756
1063
  // 解决上下文过长时 LLM 不调用工具的问题
757
1064
  this._systemPrompt = this.agent._buildSystemPrompt();
@@ -760,11 +1067,10 @@ ${truncatedContent}${truncatedNote}
760
1067
 
761
1068
  const userMessage =
762
1069
  typeof message === 'string' ? { role: 'user', content: message } : message;
763
- //console.log('System Prompt:', this._systemPrompt);
764
- this._messages.push(userMessage);
1070
+ messages.push(userMessage);
765
1071
 
766
1072
  // 检查是否需要压缩上下文(包括工具定义)
767
- const messagesTokens = this._countMessagesTokens(this._messages);
1073
+ const messagesTokens = this._countMessagesTokens(messages);
768
1074
  const toolsTokens = this._countToolsTokens();
769
1075
  const systemPromptTokens = this._countTokens(this._systemPrompt);
770
1076
  const totalTokens = messagesTokens + toolsTokens + systemPromptTokens;
@@ -774,55 +1080,76 @@ ${truncatedContent}${truncatedNote}
774
1080
  logger.info(
775
1081
  `Context large (${totalTokens}/${this._maxContextTokens} tokens = msgs:${messagesTokens} + tools:${toolsTokens} + sys:${systemPromptTokens}), compressing...`
776
1082
  );
777
- // 使用带超时控制的压缩
778
- await this._compressContext(sessionId);
1083
+ // 使用带超时控制的压缩(传入 messages 引用)
1084
+ await this._compressContext(sessionId, messages, messageStore);
779
1085
  }
780
1086
 
781
1087
  const maxSteps = options.maxSteps || this._maxSteps;
782
1088
  const tools = this._getAITools(tool);
783
1089
 
1090
+ // 准备传给 agent 的消息
1091
+ const prepareStepChainStream = async ({ stepNumber, messages }) => {
1092
+ try {
1093
+ // 1. 验证消息格式
1094
+ if (!Array.isArray(messages)) {
1095
+ logger.warn('prepareStep received non-array messages');
1096
+ return messages;
1097
+ }
1098
+
1099
+ // 2. 消息数量超过阈值才修剪
1100
+ if (messages.length <= 50) {
1101
+ return messages;
1102
+ }
1103
+
1104
+ // 3. 保留配对完整的消息
1105
+ const pruned = this._prepareMessagesForAI(messages);
1106
+ return pruned;
1107
+ } catch (err) {
1108
+ logger.error('prepareStep error:', err.message);
1109
+ return messages; // 出错时返回原消息
1110
+ }
1111
+ };
1112
+
784
1113
  if (!this._aiClient) {
785
1114
  throw new Error('AI client not configured.');
786
1115
  }
787
1116
 
788
- // 准备传给 agent 的消息
789
- // ToolLoopAgent 会自动处理消息格式,不需要手动 prune
1117
+ // 不使用 prepareStep,消息格式已在 _saveHistoryToSession 中处理
790
1118
  const agent = new ToolLoopAgent({
791
1119
  model: this._aiClient,
792
1120
  instructions: this._systemPrompt,
793
1121
  tools: tools,
794
- //stopWhen: stepCountIs(maxSteps),
795
- prepareStep: async ({ stepNumber, messages }) =>
796
- this._prepareStepForPruning(stepNumber, messages),
1122
+ stopWhen: stepCountIs(30),
1123
+ prepareStep: prepareStepChainStream,
797
1124
  });
798
1125
 
799
- // 使用 runWithContext 让工具执行时能获取 sessionId
800
- const result = await framework.runWithContext(context, async () => {
801
- return agent.generate({ messages: this._messages, ...this.providerOptions });
1126
+ // 使用 runInSession 让工具执行时能获取 sessionId(Per-Session 隔离)
1127
+ const result = await framework.runInSession(sessionId, {}, async () => {
1128
+ return agent.generate({ messages, ...this.providerOptions });
802
1129
  });
803
1130
 
804
- this._messages.push(...result.response.messages);
1131
+ messages.push(...result.response.messages);
1132
+
1133
+ // 触发 agent:message 事件,让 memory 插件可以自动提取记忆
1134
+ const userMsg = messages[messages.length - result.response.messages.length - 1];
1135
+ this.emit('message', { userMessage: userMsg, assistantResponse: result.text });
805
1136
 
806
1137
  // 生成后检查:如果消息太长,下次需要压缩
807
- const afterTokens = this._countMessagesTokens(this._messages);
1138
+ const afterTokens = this._countMessagesTokens(messages);
808
1139
  if (afterTokens > this._maxContextTokens * 0.8) {
809
1140
  logger.info(`After generation: ${afterTokens} tokens, will compress on next turn`);
810
1141
  }
811
1142
 
812
- // 保存聊天历史到 session
813
- if (sessionId) {
814
- this._saveHistoryToSession(sessionId, this._messages);
815
- }
816
-
817
1143
  return {
818
1144
  success: true,
819
1145
  message: result.text || '',
820
1146
  stepCount: result.stepCount || 1,
821
1147
  };
822
- } catch (err) {
823
- this.emit('error', { error: err.message });
824
- // 抛出错误而不是返回错误响应,让 Agent 能捕获
825
- throw err;
1148
+ } finally {
1149
+ // 校验并修复消息中的不完整工具调用
1150
+ this._validateToolCalls(messages);
1151
+ // 确保保存聊天历史到 session(无论成功还是失败)
1152
+ messageStore.save();
826
1153
  }
827
1154
  }
828
1155
 
@@ -833,19 +1160,13 @@ ${truncatedContent}${truncatedNote}
833
1160
  */
834
1161
  async *chatStream(message, options = {}) {
835
1162
  const sessionId = options.sessionId || null;
836
- const context = { sessionId, isStream: true };
837
1163
  const framework = this.agent.framework;
838
1164
 
839
- try {
840
- //从 session 加载聊天历史
841
- if (sessionId) {
842
- const savedMessages = this._loadHistoryFromSession(sessionId);
843
- if (savedMessages.length > 0) {
844
- this._messages = savedMessages;
845
- logger.info(`Loaded ${savedMessages.length} messages from session ${sessionId}`);
846
- }
847
- }
1165
+ // 获取 Per-Session 的消息存储
1166
+ const messageStore = this._getSessionMessageStore(sessionId);
1167
+ const messages = messageStore.messages;
848
1168
 
1169
+ try {
849
1170
  // 关键:每次 chat 调用时刷新系统提示词,确保包含最新的工具/技能描述
850
1171
  // 解决上下文过长时 LLM 不调用工具的问题
851
1172
  this._systemPrompt = this.agent._buildSystemPrompt();
@@ -854,23 +1175,21 @@ ${truncatedContent}${truncatedNote}
854
1175
 
855
1176
  const userMessage =
856
1177
  typeof message === 'string' ? { role: 'user', content: message } : message;
857
- //console.log('System Prompt:', this._systemPrompt);
858
- this._messages.push(userMessage);
1178
+ messages.push(userMessage);
859
1179
 
860
1180
  // 检查是否需要压缩上下文(包括工具定义)
861
- const messagesTokens = this._countMessagesTokens(this._messages);
1181
+ const messagesTokens = this._countMessagesTokens(messages);
862
1182
  const toolsTokens = this._countToolsTokens();
863
1183
  const systemPromptTokens = this._countTokens(this._systemPrompt);
864
1184
  const totalTokens = messagesTokens + toolsTokens + systemPromptTokens;
865
1185
  const limit = this._maxContextTokens * 0.7; // 降低到 70%
866
- //console.log(messagesTokens,toolsTokens,systemPromptTokens,totalTokens,this._maxContextTokens)
867
1186
  // 对于流式调用,如果上下文太大,先压缩再开始
868
1187
  if (totalTokens > limit) {
869
1188
  logger.info(
870
1189
  `Context large (${totalTokens}/${this._maxContextTokens} tokens), compressing...`
871
1190
  );
872
1191
  // 流式调用时等待压缩完成(使用带超时控制的压缩)
873
- await this._compressContext(sessionId);
1192
+ await this._compressContext(sessionId, messages, messageStore);
874
1193
  }
875
1194
 
876
1195
  const maxSteps = options.maxSteps || this._maxSteps;
@@ -880,20 +1199,40 @@ ${truncatedContent}${truncatedNote}
880
1199
  }
881
1200
 
882
1201
  // 准备传给 agent 的消息
883
- // ToolLoopAgent 会自动处理消息格式,不需要手动 prune
1202
+ const prepareStepChainStream = async ({ stepNumber, messages }) => {
1203
+ try {
1204
+ // 1. 验证消息格式
1205
+ if (!Array.isArray(messages)) {
1206
+ logger.warn('prepareStep received non-array messages');
1207
+ return messages;
1208
+ }
1209
+
1210
+ // 2. 消息数量超过阈值才修剪
1211
+ if (messages.length <= 50) {
1212
+ return messages;
1213
+ }
1214
+
1215
+ // 3. 保留配对完整的消息
1216
+ const pruned = this._prepareMessagesForAI(messages);
1217
+ return pruned;
1218
+ } catch (err) {
1219
+ logger.error('prepareStep error:', err.message);
1220
+ return messages; // 出错时返回原消息
1221
+ }
1222
+ };
1223
+
884
1224
  const agent = new ToolLoopAgent({
885
1225
  model: this._aiClient,
886
1226
  instructions: this._systemPrompt,
887
1227
  tools: tools,
888
- //stopWhen: stepCountIs(maxSteps),
889
- prepareStep: async ({ stepNumber, messages }) =>
890
- this._prepareStepForPruning(stepNumber, messages),
1228
+ stopWhen: stepCountIs(30),
1229
+ prepareStep: prepareStepChainStream,
891
1230
  });
892
1231
 
893
- // 使用 runWithContext 让工具执行时能获取 sessionId(支持并行)
894
- const result = await framework.runWithContext(context, async () => {
1232
+ // 使用 runInSession 让工具执行时能获取 sessionId(Per-Session 隔离)
1233
+ const result = await framework.runInSession(sessionId, {}, async () => {
895
1234
  return agent.stream({
896
- messages: this._messages,
1235
+ messages,
897
1236
  ...this.providerOptions,
898
1237
  });
899
1238
  });
@@ -921,18 +1260,106 @@ ${truncatedContent}${truncatedNote}
921
1260
  }
922
1261
 
923
1262
  const finishMessages = (await result.response).messages;
924
- this._messages.push(...finishMessages);
1263
+ messages.push(...finishMessages);
925
1264
 
926
- // 保存聊天历史到 session(使用实际传给 agent 的消息一致)
927
- if (sessionId) {
928
- this._saveHistoryToSession(sessionId, this._messages);
929
- }
1265
+ // 触发 agent:message 事件,让 memory 插件可以自动提取记忆
1266
+ const userMsg = messages[messages.length - finishMessages.length - 1];
1267
+ this.emit('message', { userMessage: userMsg, assistantResponse: fullText });
930
1268
  } catch (err) {
931
1269
  this.emit('error', { error: err.message });
932
1270
  yield { type: 'error', error: err.message };
1271
+ } finally {
1272
+ // 校验并修复消息中的不完整工具调用
1273
+ this._validateToolCalls(messages);
1274
+ // 确保保存聊天历史到 session(无论成功还是失败)
1275
+ messageStore.save();
933
1276
  }
934
1277
  }
935
1278
 
1279
+ /**
1280
+ * 为 AI SDK prepareStep 准备消息
1281
+ * 确保 tool call 和 tool result 正确配对
1282
+ * @param {Array} messages - 消息列表
1283
+ * @returns {Array} 过滤后的消息数组
1284
+ * @private
1285
+ */
1286
+ _prepareMessagesForAI(messages) {
1287
+ if (messages.length <= 10) {
1288
+ return messages;
1289
+ }
1290
+
1291
+ // 收集所有 assistant 的 tool-call
1292
+ const assistantToolCalls = new Map(); // toolCallId -> message index
1293
+ for (let i = 0; i < messages.length; i++) {
1294
+ const msg = messages[i];
1295
+ if (msg.role === 'assistant' && Array.isArray(msg.content)) {
1296
+ for (const item of msg.content) {
1297
+ if (item.type === 'tool-call' && item.toolCallId) {
1298
+ assistantToolCalls.set(item.toolCallId, i);
1299
+ }
1300
+ }
1301
+ }
1302
+ }
1303
+
1304
+ // 收集配对的 tool results
1305
+ const pairedToolResults = new Set(); // 保留的 tool result indices
1306
+ for (let i = 0; i < messages.length; i++) {
1307
+ const msg = messages[i];
1308
+ if (msg.role === 'tool' && Array.isArray(msg.content)) {
1309
+ for (const item of msg.content) {
1310
+ if (item.type === 'tool-result' && item.toolCallId) {
1311
+ if (assistantToolCalls.has(item.toolCallId)) {
1312
+ pairedToolResults.add(i);
1313
+ }
1314
+ }
1315
+ }
1316
+ }
1317
+ }
1318
+
1319
+ // 过滤:保留最近的消息,但确保 tool call/result 配对完整
1320
+ const recentCount = Math.max(20, messages.length - 10);
1321
+ const minIndexToKeep = messages.length - recentCount;
1322
+
1323
+ const result = [];
1324
+ for (let i = minIndexToKeep; i < messages.length; i++) {
1325
+ const msg = messages[i];
1326
+
1327
+ if (msg.role === 'tool') {
1328
+ // 只保留有配对的 tool messages
1329
+ if (pairedToolResults.has(i)) {
1330
+ result.push(msg);
1331
+ }
1332
+ } else if (msg.role === 'assistant') {
1333
+ // 检查是否有未配对的 tool-call
1334
+ if (Array.isArray(msg.content)) {
1335
+ const hasUnpairedToolCall = msg.content.some(
1336
+ (item) => item.type === 'tool-call' && !assistantToolCalls.has(item.toolCallId)
1337
+ );
1338
+ if (hasUnpairedToolCall) {
1339
+ // 过滤掉未配对的 tool-call
1340
+ const filteredContent = msg.content.filter((item) => {
1341
+ if (item.type === 'tool-call') {
1342
+ return assistantToolCalls.has(item.toolCallId);
1343
+ }
1344
+ return true;
1345
+ });
1346
+ if (filteredContent.length > 0) {
1347
+ result.push({ ...msg, content: filteredContent });
1348
+ }
1349
+ } else {
1350
+ result.push(msg);
1351
+ }
1352
+ } else {
1353
+ result.push(msg);
1354
+ }
1355
+ } else {
1356
+ result.push(msg);
1357
+ }
1358
+ }
1359
+
1360
+ return result;
1361
+ }
1362
+
936
1363
  /**
937
1364
  * 修剪消息确保配对的 tool call/result 不被拆分
938
1365
  * @param {number} stepNumber - 当前步骤号
@@ -959,11 +1386,16 @@ ${truncatedContent}${truncatedNote}
959
1386
  }
960
1387
  }
961
1388
  }
962
- if (msg.role === 'tool' && msg.toolCallId) {
963
- if (!toolResultIndices.has(msg.toolCallId)) {
964
- toolResultIndices.set(msg.toolCallId, []);
1389
+ if (msg.role === 'tool' && Array.isArray(msg.content)) {
1390
+ // 遍历 tool message 中的所有 tool-results
1391
+ for (const item of msg.content) {
1392
+ if (item && item.type === 'tool-result' && item.toolCallId) {
1393
+ if (!toolResultIndices.has(item.toolCallId)) {
1394
+ toolResultIndices.set(item.toolCallId, []);
1395
+ }
1396
+ toolResultIndices.get(item.toolCallId).push(i);
1397
+ }
965
1398
  }
966
- toolResultIndices.get(msg.toolCallId).push(i);
967
1399
  }
968
1400
  }
969
1401
 
@@ -996,6 +1428,49 @@ ${truncatedContent}${truncatedNote}
996
1428
  return { messages: pruned };
997
1429
  }
998
1430
 
1431
+ /**
1432
+ * 校验并修复消息中的工具调用参数
1433
+ * 移除不完整的 JSON(如只有 "{" )的工具调用
1434
+ * @param {Array} messages - 消息列表
1435
+ * @private
1436
+ */
1437
+ _validateToolCalls(messages) {
1438
+ let fixedCount = 0;
1439
+
1440
+ for (const msg of messages) {
1441
+ if (msg.role !== 'assistant' || !Array.isArray(msg.content)) {
1442
+ continue;
1443
+ }
1444
+
1445
+ for (const item of msg.content) {
1446
+ if (item.type !== 'tool-call') {
1447
+ continue;
1448
+ }
1449
+
1450
+ const input = item.input;
1451
+ if (typeof input !== 'string') {
1452
+ continue;
1453
+ }
1454
+
1455
+ // 检查 input 是否是有效的 JSON(不是不完整的)
1456
+ const trimmed = input.trim();
1457
+ if (trimmed === '{' || trimmed === '' || !trimmed.startsWith('{')) {
1458
+ // 不完整的 JSON,移除这个 tool-call
1459
+ item.type = 'text';
1460
+ item.text = `(工具调用 ${item.toolName} 参数不完整,已跳过)`;
1461
+ delete item.toolCallId;
1462
+ delete item.toolName;
1463
+ delete item.input;
1464
+ fixedCount++;
1465
+ }
1466
+ }
1467
+ }
1468
+
1469
+ if (fixedCount > 0) {
1470
+ logger.info(`Fixed ${fixedCount} incomplete tool calls`);
1471
+ }
1472
+ }
1473
+
999
1474
  /**
1000
1475
  * 获取 AI SDK 格式的工具
1001
1476
  * AI SDK 6.x 需要对象形式 { toolName: tool }
@@ -1022,7 +1497,10 @@ ${truncatedContent}${truncatedNote}
1022
1497
  try {
1023
1498
  const result = await toolDef.execute(cleanedArgs, this.agent.framework);
1024
1499
  this.emit('tool-result', { name: toolName, args: cleanedArgs, result });
1025
- return result;
1500
+ if (result !== null && typeof result === 'object') {
1501
+ return JSON.stringify(result);
1502
+ }
1503
+ return String(result);
1026
1504
  } catch (err) {
1027
1505
  this.emit('tool-error', { name: toolName, args: cleanedArgs, error: err.message });
1028
1506
  return { error: err.message };
@@ -1084,7 +1562,8 @@ ${truncatedContent}${truncatedNote}
1084
1562
  * 销毁
1085
1563
  */
1086
1564
  destroy() {
1087
- this._messages = [];
1565
+ this._sessionMessageStores.clear();
1566
+ this._sessionMessageStores = new Map();
1088
1567
  this._tools.clear();
1089
1568
  this._encoder = null;
1090
1569
  this.removeAllListeners();