@myassis/gateway 1.0.1 → 1.0.2

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 (50) hide show
  1. package/dist/api/index.js +69 -59
  2. package/dist/config/index.js +2 -2
  3. package/dist/index.js +85 -11
  4. package/dist/middleware/auth.js +9 -25
  5. package/dist/middleware/errorHandler.js +1 -1
  6. package/dist/routes/agent.js +30 -19
  7. package/dist/routes/auth.js +35 -26
  8. package/dist/routes/chat.js +1 -1
  9. package/dist/routes/models.js +11 -14
  10. package/dist/routes/service.js +3 -6
  11. package/dist/routes/settings.js +11 -9
  12. package/dist/routes/skillHub.js +10 -6
  13. package/dist/routes/skills.js +13 -10
  14. package/dist/routes/tasks.js +6 -3
  15. package/dist/routes/upload.js +5 -2
  16. package/dist/routes/version.js +4 -4
  17. package/dist/services/LocalTaskService.js +1 -1
  18. package/dist/services/NotificationService.js +1 -1
  19. package/dist/services/ServiceManager.js +12 -12
  20. package/dist/services/TaskSchedulerService.js +18 -9
  21. package/dist/services/TaskService.js +23 -20
  22. package/dist/services/WebSocketService.js +16 -6
  23. package/dist/services/agent/Agent.js +7 -7
  24. package/dist/services/agent/AgentManager.js +25 -17
  25. package/dist/services/agent/AgentStore.js +1 -1
  26. package/dist/services/dataService.js +60 -70
  27. package/dist/services/index.js +1 -1
  28. package/dist/services/llm/LLMClient.js +11 -9
  29. package/dist/services/memory/MemoryManager.js +177 -14
  30. package/dist/services/session/MigrationManager.js +1 -1
  31. package/dist/services/session/Session.js +70 -46
  32. package/dist/services/session/SessionManager.js +12 -5
  33. package/dist/services/session/SessionStore.js +1 -27
  34. package/dist/services/session/index.js +1 -1
  35. package/dist/services/systemPrompt.js +8 -4
  36. package/dist/services/task/PushTokenStore.js +1 -19
  37. package/dist/services/task/TaskStore.js +1 -20
  38. package/dist/services/tools/edit.js +122 -148
  39. package/dist/services/tools/exec.js +1 -1
  40. package/dist/services/tools/fetch.js +1 -1
  41. package/dist/services/tools/file.js +4 -9
  42. package/dist/services/tools/index.js +4 -3
  43. package/dist/services/tools/model.js +8 -6
  44. package/dist/services/tools/sessionsSpawn.js +54 -0
  45. package/dist/services/tools/skill.js +13 -12
  46. package/dist/services/tools/task.js +2 -4
  47. package/dist/stores/authStore.js +52 -66
  48. package/dist/stores/persistStore.js +37 -3
  49. package/package.json +7 -3
  50. package/scripts/postbuild.js +39 -0
@@ -1,5 +1,5 @@
1
1
  import { persistStore } from "@/stores";
2
- import { getLogger } from '@pocketclaw/shared';
2
+ import { getLogger } from '@myassis/shared';
3
3
  import fs from 'fs/promises';
4
4
  const logger = getLogger('LLMClient');
5
5
  /**
@@ -249,6 +249,7 @@ export class LLMClient {
249
249
  }
250
250
  else {
251
251
  // OpenAI compatible API
252
+ let existAttachments = false;
252
253
  const body = {
253
254
  model: model.modelId,
254
255
  messages: this.messages.map((m) => {
@@ -319,14 +320,8 @@ export class LLMClient {
319
320
  }
320
321
  // 处理用户消息中的附件
321
322
  if (m.role === 'user' && m.attachments && m.attachments.length > 0) {
323
+ existAttachments = true;
322
324
  const contentArray = [];
323
- // 如果有文本内容,先添加文本
324
- if (m.content && m.content.trim()) {
325
- contentArray.push({
326
- type: 'text',
327
- text: m.content,
328
- });
329
- }
330
325
  // 添加附件
331
326
  for (const attachment of m.attachments) {
332
327
  if (attachment.type === 'image') {
@@ -373,6 +368,13 @@ export class LLMClient {
373
368
  }
374
369
  }
375
370
  }
371
+ // 如果有文本内容,添加文本
372
+ if (m.content && m.content.trim()) {
373
+ contentArray.push({
374
+ type: 'text',
375
+ text: m.content,
376
+ });
377
+ }
376
378
  return [{
377
379
  role: m.role,
378
380
  content: contentArray,
@@ -392,7 +394,7 @@ export class LLMClient {
392
394
  }
393
395
  const tokens = JSON.stringify(body).length;
394
396
  logger.debug('tokens', tokens);
395
- if (tokens > 100000) {
397
+ if (tokens > 200000) {
396
398
  logger.warn('message is too large and has writen to the file debug.json');
397
399
  await fs.writeFile('debug.json', JSON.stringify(body.messages), 'utf-8');
398
400
  throw Error('exceed max message tokens');
@@ -1,7 +1,8 @@
1
- import { getLogger } from '@pocketclaw/shared';
1
+ import { getLogger } from '@myassis/shared';
2
2
  const logger = getLogger('MemoryManager');
3
3
  import { toModel } from '../models';
4
4
  import { modelsService } from '../dataService';
5
+ import { authStore } from '@/stores';
5
6
  import { LLMClient } from '../llm/LLMClient';
6
7
  import { appConfig } from '@/config';
7
8
  const DEFAULT_CONFIG = {
@@ -16,9 +17,11 @@ export class MemoryManager {
16
17
  session;
17
18
  signal;
18
19
  config = DEFAULT_CONFIG;
19
- constructor(session, signal) {
20
+ childAgent;
21
+ constructor(session, signal, childAgent) {
20
22
  this.session = session;
21
23
  this.signal = signal;
24
+ this.childAgent = childAgent;
22
25
  }
23
26
  toSummaryMessage(summary, createdAt, modelName) {
24
27
  return {
@@ -32,7 +35,11 @@ export class MemoryManager {
32
35
  }
33
36
  async getHistoryMessagesAsync() {
34
37
  // 1. 获取所有消息
35
- const messages = this.session.getMessages();
38
+ let messages = this.session.getMessages();
39
+ if (this.childAgent) {
40
+ messages.splice(-1);
41
+ logger.debug('history', messages);
42
+ }
36
43
  // 2. 消息少于摘要配置的阈值数,直接返回
37
44
  logger.debug('message length:', messages.length);
38
45
  if (messages.length < this.config.summaryThreshold) {
@@ -77,32 +84,120 @@ export class MemoryManager {
77
84
  }
78
85
  }
79
86
  }
87
+ /**
88
+ * 格式化工具调用内容,提取关键信息供摘要模型理解
89
+ */
90
+ formatToolCalls(toolCalls) {
91
+ if (!toolCalls || toolCalls.length === 0)
92
+ return '';
93
+ return toolCalls.map(tc => {
94
+ const parts = [];
95
+ // 外层 toolCall 节点
96
+ if (tc.content)
97
+ parts.push(`思路: ${tc.content}`);
98
+ if (tc.reasoningContent)
99
+ parts.push(`推理: ${tc.reasoningContent}`);
100
+ // 内层工具调用详情
101
+ if (tc.toolCalls && tc.toolCalls.length > 0) {
102
+ const callDetails = tc.toolCalls.map((call) => {
103
+ const name = `${call.toolName}${call.actionName ? '.' + call.actionName : ''}`;
104
+ let detail = name;
105
+ if (call.input) {
106
+ try {
107
+ const input = typeof call.input === 'string' ? JSON.parse(call.input) : call.input;
108
+ // 提取关键输入信息,避免过长
109
+ const inputStr = JSON.stringify(input).slice(0, 500);
110
+ detail += ` 输入: ${inputStr}`;
111
+ }
112
+ catch {
113
+ detail += ` 输入: ${String(call.input).slice(0, 500)}`;
114
+ }
115
+ }
116
+ if (call.output) {
117
+ const outputStr = typeof call.output === 'string' ? call.output : JSON.stringify(call.output);
118
+ detail += ` 结果: ${outputStr.slice(0, 500)}`;
119
+ }
120
+ if (call.status === 'error')
121
+ detail += ' [失败]';
122
+ return detail;
123
+ }).join('; ');
124
+ parts.push(`工具调用: ${callDetails}`);
125
+ }
126
+ return parts.join('\n');
127
+ }).join('\n');
128
+ }
129
+ /**
130
+ * 格式化单条消息,提取有意义的内容
131
+ */
132
+ formatMessage(m) {
133
+ const role = m.role === 'user' ? '用户' : m.role === 'assistant' ? '助手' : '系统';
134
+ let content = m.content || '';
135
+ // 附加工具调用信息
136
+ const toolCallInfo = this.formatToolCalls(m.toolCalls);
137
+ if (toolCallInfo) {
138
+ content += `\n[${toolCallInfo}]`;
139
+ }
140
+ // 截断过长内容
141
+ if (content.length > 2000) {
142
+ content = content.slice(0, 2000) + '...(内容过长已截断)';
143
+ }
144
+ return `${role}: ${content}`;
145
+ }
80
146
  /**
81
147
  * 生成摘要提示词
82
148
  */
83
149
  buildSummaryPrompt(messages, lastSummary) {
84
150
  const conversation = messages
85
151
  .filter((m) => m.role === 'user' || m.role === 'assistant')
86
- .map((m) => `${m.role === 'user' ? '用户' : '助手'}: ${m.content}${m.toolCalls ? JSON.stringify(m.toolCalls) : ''}`)
87
- .join('\n');
88
- return `请分析以下对话内容,生成一个简洁的摘要,保留关键信息、错误信息和上下文。
152
+ .map((m) => this.formatMessage(m))
153
+ .join('\n\n');
154
+ return `你是一个对话摘要专家。请分析以下对话内容,生成一份结构化的摘要。
155
+
156
+ 重要规则:
157
+ 1. 如果对话涉及代码开发/项目工作,必须详细总结项目结构,包括:技术栈、目录结构、关键文件及其职责、模块间关系
158
+ 2. 项目结构描述不限字数,务必完整清晰
159
+ 3. 保留所有关键决策、修改内容和上下文
160
+ 4. 保留错误信息和解决方案,避免重复犯错
161
+ 5. 如果有上一轮摘要,合并其中的关键信息
89
162
 
90
- 对话内容:
163
+ ${lastSummary ? '=== 上一轮摘要 ===\n' + lastSummary + '\n\n' : ''}=== 对话内容 ===
91
164
  ${conversation}
92
165
 
93
- ${lastSummary ? '上一次内容摘要:\n' + lastSummary : ''}
166
+ === 输出格式 ===
167
+ 请按以下格式输出(各部分字数不限,以完整准确为优先):
168
+
169
+ 【摘要】简要概括对话主题和关键进展
170
+
171
+ 【项目结构】(如涉及项目开发)详细描述:
172
+ - 技术栈与框架
173
+ - 目录结构与各目录职责
174
+ - 关键文件及其作用
175
+ - 模块间依赖关系
176
+ - 已完成和待完成的功能
94
177
 
95
- 请用以下格式返回摘要(只返回摘要,不要其他内容):
96
- 【摘要】:简要概括对话主题和关键信息(不超过100字)
97
- 【要点】:列出2-5个关键要点
98
- 【错误】:列出犯过的错误`;
178
+ 【关键决策】列出重要决策及原因
179
+
180
+ 【要点】列出3-8个关键要点
181
+
182
+ 【错误与修复】列出遇到的错误及解决方案,标注"⚠️"避免重复`;
99
183
  }
100
184
  /**
101
- * 生成对话摘要
185
+ * 生成对话摘要(支持分层摘要)
102
186
  */
103
187
  async generateSummaryAsync(messages, lastSummary) {
188
+ const MAX_INPUT_CHARS = 30000; // 单次摘要最大输入字符数
189
+ // 格式化所有消息用于估算长度
190
+ const formattedMessages = messages
191
+ .filter((m) => m.role === 'user' || m.role === 'assistant')
192
+ .map((m) => this.formatMessage(m));
193
+ const totalChars = formattedMessages.join('').length;
194
+ if (totalChars > MAX_INPUT_CHARS && formattedMessages.length > 20) {
195
+ // 分层摘要:分批生成子摘要,再合并
196
+ return await this.hierarchicalSummary(formattedMessages, lastSummary);
197
+ }
104
198
  const summaryPrompt = this.buildSummaryPrompt(messages, lastSummary);
105
- const llmClient = new LLMClient((await modelsService.list()).data.map((x) => toModel(x)), [{ role: 'user', content: summaryPrompt }], this.signal, []);
199
+ const token = authStore.get(this.session.userId).accessToken;
200
+ const llmClient = new LLMClient((await modelsService.list(token)).data.map((x) => toModel(x)), [{ role: 'user', content: summaryPrompt }], this.signal, []);
106
201
  const response = await llmClient.Chat();
107
202
  const content = (response.content || '').trim();
108
203
  if (!content) {
@@ -114,4 +209,72 @@ ${lastSummary ? '上一次内容摘要:\n' + lastSummary : ''}
114
209
  }
115
210
  return content;
116
211
  }
212
+ /**
213
+ * 分层摘要:将消息分批生成子摘要,再合并为最终摘要
214
+ */
215
+ async hierarchicalSummary(formattedMessages, lastSummary) {
216
+ const BATCH_SIZE = 15; // 每批消息数
217
+ const subSummaries = [];
218
+ for (let i = 0; i < formattedMessages.length; i += BATCH_SIZE) {
219
+ if (this.signal.aborted)
220
+ break;
221
+ const batch = formattedMessages.slice(i, i + BATCH_SIZE);
222
+ const batchPrompt = `请简洁总结以下对话片段的关键信息,特别关注:项目结构、关键决策、错误及修复。\n\n${batch.join('\n\n')}`;
223
+ const token = authStore.get(this.session.userId).accessToken;
224
+ const llmClient = new LLMClient((await modelsService.list(token)).data.map((x) => toModel(x)), [{ role: 'user', content: batchPrompt }], this.signal, []);
225
+ const response = await llmClient.Chat();
226
+ const subContent = (response.content || '').trim();
227
+ if (subContent) {
228
+ subSummaries.push(subContent);
229
+ }
230
+ }
231
+ // 合并所有子摘要
232
+ if (subSummaries.length === 0) {
233
+ return lastSummary || '(摘要生成失败)';
234
+ }
235
+ if (subSummaries.length === 1) {
236
+ return subSummaries[0];
237
+ }
238
+ // 合并子摘要为最终摘要
239
+ const mergePrompt = this.buildMergePrompt(subSummaries, lastSummary);
240
+ const token = authStore.get(this.session.userId).accessToken;
241
+ const mergeLlmClient = new LLMClient((await modelsService.list(token)).data.map((x) => toModel(x)), [{ role: 'user', content: mergePrompt }], this.signal, []);
242
+ const mergeResponse = await mergeLlmClient.Chat();
243
+ const mergedContent = (mergeResponse.content || '').trim();
244
+ return mergedContent || subSummaries.join('\n\n');
245
+ }
246
+ /**
247
+ * 构建合并摘要提示词
248
+ */
249
+ buildMergePrompt(subSummaries, lastSummary) {
250
+ const summaries = subSummaries
251
+ .map((s, i) => `--- 片段 ${i + 1} ---\n${s}`)
252
+ .join('\n\n');
253
+ return `你是一个对话摘要专家。以下是多个对话片段的摘要,请合并为一份完整的结构化摘要。
254
+
255
+ 重要规则:
256
+ 1. 合并重复信息,去重项目结构描述
257
+ 2. 如果涉及项目开发,务必完整保留项目结构信息(技术栈、目录结构、关键文件、模块关系)
258
+ 3. 按时间顺序整理决策和进展
259
+ 4. 保留所有错误信息及解决方案
260
+
261
+ ${lastSummary ? '=== 上一轮摘要 ===\n' + lastSummary + '\n\n' : ''}=== 分片段摘要 ===
262
+ ${summaries}
263
+
264
+ === 输出格式 ===
265
+ 【摘要】简要概括对话主题和关键进展
266
+
267
+ 【项目结构】(如涉及项目开发)详细描述:
268
+ - 技术栈与框架
269
+ - 目录结构与各目录职责
270
+ - 关键文件及其作用
271
+ - 模块间依赖关系
272
+ - 已完成和待完成的功能
273
+
274
+ 【关键决策】列出重要决策及原因
275
+
276
+ 【要点】列出3-8个关键要点
277
+
278
+ 【错误与修复】列出遇到的错误及解决方案,标注"⚠️"避免重复`;
279
+ }
117
280
  }
@@ -1,6 +1,6 @@
1
1
  import path from 'path';
2
2
  import fs from 'fs';
3
- import { getLogger } from '@pocketclaw/shared';
3
+ import { getLogger } from '@myassis/shared';
4
4
  const logger = getLogger('MigrationManager');
5
5
  export class MigrationManager {
6
6
  db;
@@ -1,15 +1,16 @@
1
1
  import { v4 as uuidv4 } from 'uuid';
2
- import { getLogger } from '@pocketclaw/shared';
2
+ import { getLogger } from '@myassis/shared';
3
3
  import { SessionStore } from './SessionStore';
4
4
  import { AgentStore } from '@/services/agent/AgentStore';
5
+ import { authStore } from '@/stores/authStore';
5
6
  import { toModel } from '@/services/models';
6
7
  import { executeTool, getToolDefinitions } from '@/services/tools';
7
8
  import { getSystemPromptAsync } from '../systemPrompt';
8
9
  import { LLMClient, parseAruments } from '../llm/LLMClient';
9
10
  import { modelsService, settingsService } from '@/services/dataService';
10
11
  import { MemoryManager } from '../memory/MemoryManager';
11
- import { sessionManager } from './SessionManager';
12
12
  import { appConfig } from '@/config';
13
+ import { getSessionManager } from './SessionManager';
13
14
  const logger = getLogger('Session');
14
15
  const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
15
16
  // AgentStore 单例 - 需要通过 SessionStore 获取数据库实例
@@ -46,7 +47,6 @@ export class Session {
46
47
  unreadCount = 0;
47
48
  currentMessageId = null;
48
49
  abortController = null;
49
- toolCalls;
50
50
  toInsertMessages;
51
51
  constructor(data) {
52
52
  this.id = data.id;
@@ -103,16 +103,13 @@ export class Session {
103
103
  return this.messages.reduce((total, msg) => total + msg.content.length, 0);
104
104
  }
105
105
  // ========== Message Operations ==========
106
- /**
107
- * Add user message
108
- */
109
- addUserMessage(content, userMessageId, attachments) {
106
+ getUserMessage(content, userMessageId, attachments) {
110
107
  // 将附件统一为 Attachment 对象格式
111
108
  const normalizedAttachments = (attachments || []).map((att) => {
112
109
  if (typeof att === 'string') {
113
110
  // URL 字符串格式,转换为对象
114
111
  const url = att;
115
- const name = url.split('/').pop() || 'attachment';
112
+ const name = url.split('/').pop().split('?')[0] || 'attachment';
116
113
  const ext = name.split('.').pop()?.toLowerCase() || '';
117
114
  const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg'];
118
115
  const videoExts = ['mp4', 'avi', 'mov', 'mkv', 'webm'];
@@ -145,6 +142,13 @@ export class Session {
145
142
  createdAt: Date.now(),
146
143
  modelName: ''
147
144
  };
145
+ return message;
146
+ }
147
+ /**
148
+ * Add user message
149
+ */
150
+ addUserMessage(content, userMessageId, attachments) {
151
+ const message = this.getUserMessage(content, userMessageId, attachments);
148
152
  this.messages.push(message);
149
153
  this.toInsertMessages.push(message);
150
154
  return message;
@@ -375,15 +379,27 @@ export class Session {
375
379
  * Stream chat response (SSE) - 使用 ModelSelector 根据选择的模型调用
376
380
  * 支持工具调用流程
377
381
  */
378
- async streamChat(content, attachments = [], res, userMessageId, assistantMessageId) {
382
+ async streamChat(content, attachments = [], res, userMessageId, assistantMessageId, childAgent = false) {
383
+ // 如果正在生成,等待当前生成完成(最多等待 2 秒)
384
+ if (this.isGenerating) {
385
+ const maxWait = 2000;
386
+ const startTime = Date.now();
387
+ while (this.isGenerating && Date.now() - startTime < maxWait) {
388
+ await sleep(100);
389
+ }
390
+ // 超时后强制中断
391
+ if (this.isGenerating) {
392
+ this.stopGenerating();
393
+ }
394
+ }
379
395
  // Add user message
380
396
  this.getAbortController();
381
397
  this.isGenerating = true;
382
- this.toolCalls = [];
383
- const settings = await settingsService.get();
398
+ const token = authStore.get(this.userId).accessToken;
399
+ const settings = await settingsService.get(token);
384
400
  const streamDelay = this.getStreamDelay(settings.streamSpeed);
385
401
  this.currentMessageId = assistantMessageId;
386
- const memoryManager = new MemoryManager(this, this.abortController.signal);
402
+ const memoryManager = new MemoryManager(this, this.abortController.signal, childAgent);
387
403
  const historyMessages = await memoryManager.getHistoryMessagesAsync();
388
404
  // SSE 辅助方法:res 为 null 时跳过写入(本地执行模式)
389
405
  const sendSSE = (res, data) => {
@@ -433,8 +449,13 @@ export class Session {
433
449
  messages.unshift({ role: 'system', content: systemPrompt, attachments: [] });
434
450
  }
435
451
  //插入用户消息
436
- this.addUserMessage(content, userMessageId, attachments);
437
- messages.push(this.messages.at(-1));
452
+ if (!childAgent) {
453
+ this.addUserMessage(content, userMessageId, attachments);
454
+ messages.push(this.messages.at(-1));
455
+ }
456
+ else {
457
+ messages.push(this.getUserMessage(content, userMessageId, attachments));
458
+ }
438
459
  let toolCall = null;
439
460
  let modelNames = [];
440
461
  //用于后续的消息滑动处理
@@ -450,6 +471,7 @@ export class Session {
450
471
  message.reasoning_content = message.reasoning_content.substring(0, 100) + '...(内容过长已截断)';
451
472
  }
452
473
  };
474
+ const toolCalls = [];
453
475
  // 递归处理函数(支持多轮工具调用)
454
476
  const processModelResponse = async () => {
455
477
  if (!this.abortController || !this.abortController.signal || this.abortController.signal.aborted) {
@@ -460,7 +482,7 @@ export class Session {
460
482
  // 清理旧工具调用:只保留最近 keep 轮的完整上下文
461
483
  {
462
484
  const MAX_TOOL_PAIRS = keep;
463
- const allToolCallItems = this.toolCalls.flatMap(x => x.toolCalls);
485
+ const allToolCallItems = toolCalls.flatMap(x => x.toolCalls);
464
486
  // 1) 清理 this.toolCalls 中超出 keep 的 output(供前端展示用)
465
487
  allToolCallItems.forEach((item, index) => {
466
488
  if (index < allToolCallItems.length - MAX_TOOL_PAIRS) {
@@ -498,7 +520,7 @@ export class Session {
498
520
  }
499
521
  // 获取本地工具定义
500
522
  const tools = getToolDefinitions();
501
- const models = (await modelsService.list()).data.map(x => toModel(x));
523
+ const models = (await modelsService.list(token)).data.map(x => toModel(x));
502
524
  // 使用 LLMClient 进行流式调用
503
525
  if (!this.abortController || !this.abortController.signal || this.abortController.signal.aborted) {
504
526
  throw Error('aborted');
@@ -568,7 +590,7 @@ export class Session {
568
590
  }
569
591
  // 执行工具调用并收集结果
570
592
  const toolResults = [];
571
- for (const pendingToolCall of llmResult.toolCalls) {
593
+ const startToolCall = async (pendingToolCall) => {
572
594
  // 发送工具开始执行事件
573
595
  const toolStartEvent = {
574
596
  type: 'tool_call_execute',
@@ -581,7 +603,7 @@ export class Session {
581
603
  };
582
604
  sendSSE(res, toolStartEvent);
583
605
  try {
584
- const result = await executeTool(pendingToolCall.toolName, parseAruments(pendingToolCall.input), this.id, this.currentMessageId);
606
+ const result = await executeTool(pendingToolCall.toolName, parseAruments(pendingToolCall.input), this.id, this.currentMessageId, this.userId);
585
607
  toolResults.push({
586
608
  id: pendingToolCall.id,
587
609
  name: pendingToolCall.toolName,
@@ -638,8 +660,9 @@ export class Session {
638
660
  };
639
661
  sendSSE(res, errorEvent);
640
662
  }
641
- }
642
- this.toolCalls.push(toolCall);
663
+ };
664
+ await Promise.all(llmResult.toolCalls.map(x => startToolCall(x)));
665
+ toolCalls.push(toolCall);
643
666
  // 将工具结果添加到消息历史(格式化为 tool 角色的消息)
644
667
  for (const result of toolResults) {
645
668
  const toolResultMessage = {
@@ -655,7 +678,7 @@ export class Session {
655
678
  messages.push(toolResultMessage);
656
679
  }
657
680
  // 继续递归处理(带上工具结果继续调用模型)
658
- await processModelResponse();
681
+ return await processModelResponse();
659
682
  }
660
683
  else {
661
684
  // ========== 没有工具调用,保存消息并结束 ==========
@@ -672,34 +695,43 @@ export class Session {
672
695
  await sleep(streamDelay);
673
696
  }
674
697
  }
675
- this.addAssistantMessage(llmResult.content || llmResult.reasoningContent, this.toolCalls, [...new Set(modelNames)].join(','), this.currentMessageId);
698
+ if (!childAgent) {
699
+ this.addAssistantMessage(llmResult.content || llmResult.reasoningContent, toolCalls, [...new Set(modelNames)].join(','), this.currentMessageId);
700
+ }
676
701
  sendSSE(res, { type: 'complete', modelName: [...new Set(modelNames)].join(",") });
677
702
  sendSSE(res, { type: '[DONE]' });
703
+ return llmResult.content;
678
704
  }
679
705
  };
680
706
  try {
681
- await processModelResponse();
707
+ const result = await processModelResponse();
708
+ return { success: true, data: result };
682
709
  }
683
710
  catch (error) {
684
711
  logger.error('stream chat ', error);
685
- if (this.toolCalls) {
686
- this.addAssistantMessage('', this.toolCalls, [...new Set(modelNames)].join(','), this.currentMessageId);
687
- }
688
- if (error.message !== 'aborted') {
689
- sendSSE(res, { type: 'error', error: error.message });
712
+ if (!childAgent) {
713
+ if (toolCalls) {
714
+ this.addAssistantMessage('', toolCalls, [...new Set(modelNames)].join(','), this.currentMessageId);
715
+ }
716
+ if (error.message !== 'aborted') {
717
+ sendSSE(res, { type: 'error', error: error.message });
718
+ }
690
719
  }
720
+ return { success: false, error: error.message };
691
721
  }
692
722
  finally {
693
- this.isGenerating = false;
694
- this.abortController = null;
695
- if (res) {
696
- res.end();
697
- }
698
- // 非当前查看的 session,助手回复完成后增加未读计数
699
- sessionManager.onMessageComplete(this.id);
700
- // 只在非 abort 的情况下保存(abort 由 stopGenerating 负责保存)
701
- if (this.currentMessageId !== null) {
702
- this.saveMessage();
723
+ if (!childAgent) {
724
+ this.isGenerating = false;
725
+ this.abortController = null;
726
+ if (res) {
727
+ res.end();
728
+ }
729
+ // 非当前查看的 session,助手回复完成后增加未读计数
730
+ getSessionManager(this.userId).onMessageComplete(this.id);
731
+ // 只在非 abort 的情况下保存(abort 由 stopGenerating 负责保存)
732
+ if (this.currentMessageId !== null) {
733
+ this.saveMessage();
734
+ }
703
735
  }
704
736
  }
705
737
  }
@@ -712,14 +744,6 @@ export class Session {
712
744
  this.abortController.abort();
713
745
  this.abortController = null;
714
746
  }
715
- // stopGenerating 负责保存中断的消息,streamChat 的 finally 不再重复保存
716
- if (this.currentMessageId) {
717
- if (this.toolCalls && this.toolCalls.length > 0) {
718
- this.addAssistantMessage('', this.toolCalls, this.currentMessageId);
719
- }
720
- this.saveMessage();
721
- this.currentMessageId = null;
722
- }
723
747
  }
724
748
  /**
725
749
  * 获取 AbortController 用于中断请求
@@ -1,5 +1,5 @@
1
1
  import { v4 as uuidv4 } from 'uuid';
2
- import { getLogger } from '@pocketclaw/shared';
2
+ import { getLogger } from '@myassis/shared';
3
3
  const logger = getLogger('SessionManager');
4
4
  import { sessionStore } from './SessionStore';
5
5
  import { authStore } from '@/stores/authStore';
@@ -11,7 +11,9 @@ import { Session } from './Session';
11
11
  export class SessionManager {
12
12
  sessions = null;
13
13
  initialized = false;
14
- constructor() {
14
+ userId;
15
+ constructor(userId) {
16
+ this.userId = userId;
15
17
  }
16
18
  /**
17
19
  * Initialize - Load all sessions from database on startup
@@ -40,7 +42,7 @@ export class SessionManager {
40
42
  * Get current user ID
41
43
  */
42
44
  getCurrentUserId() {
43
- const user = authStore.getUser();
45
+ const user = authStore.getUser(this.userId);
44
46
  return user ? String(user.id) : null;
45
47
  }
46
48
  /**
@@ -251,5 +253,10 @@ export class SessionManager {
251
253
  return sessionStore.getMigrationStatus();
252
254
  }
253
255
  }
254
- // Export singleton
255
- export const sessionManager = new SessionManager();
256
+ export let sessionManagerMap = new Map();
257
+ export function getSessionManager(userId) {
258
+ if (!sessionManagerMap.has(userId)) {
259
+ sessionManagerMap.set(userId, new SessionManager(userId));
260
+ }
261
+ return sessionManagerMap.get(userId);
262
+ }
@@ -1,7 +1,7 @@
1
1
  import Database from 'better-sqlite3';
2
2
  import path from 'path';
3
3
  import fs from 'fs';
4
- import { getLogger } from '@pocketclaw/shared';
4
+ import { getLogger } from '@myassis/shared';
5
5
  import { MigrationManager } from './MigrationManager';
6
6
  const logger = getLogger('SessionStore');
7
7
  /**
@@ -22,36 +22,10 @@ export class SessionStore {
22
22
  const migrationsDir = path.join(process.cwd(), 'migrations');
23
23
  this.migrationManager = new MigrationManager(this.db, migrationsDir);
24
24
  this.migrationManager.migrate();
25
- this.ensureColumns();
26
25
  logger.info(`Initialized at ${dbPath}`);
27
26
  // 注册到全局供 Session 使用
28
27
  global.__sessionStore = this;
29
28
  }
30
- /**
31
- * Ensure required columns exist
32
- */
33
- ensureColumns() {
34
- // Ensure agent_id column in sessions
35
- const sessionTableInfo = this.db.prepare("PRAGMA table_info(sessions)").all();
36
- const hasAgentId = sessionTableInfo.some(col => col.name === 'agent_id');
37
- if (!hasAgentId) {
38
- this.db.exec("ALTER TABLE sessions ADD COLUMN agent_id TEXT");
39
- logger.info('Added agent_id column to sessions');
40
- }
41
- // Ensure tool_calls column in messages
42
- const msgTableInfo = this.db.prepare("PRAGMA table_info(messages)").all();
43
- const hasToolCalls = msgTableInfo.some(col => col.name === 'tool_calls');
44
- if (!hasToolCalls) {
45
- this.db.exec("ALTER TABLE messages ADD COLUMN tool_calls TEXT");
46
- logger.info('Added tool_calls column to messages');
47
- }
48
- // Ensure feedback column in messages
49
- const hasFeedback = msgTableInfo.some(col => col.name === 'feedback');
50
- if (!hasFeedback) {
51
- this.db.exec("ALTER TABLE messages ADD COLUMN feedback TEXT");
52
- logger.info('Added feedback column to messages');
53
- }
54
- }
55
29
  /**
56
30
  * Provide transaction method
57
31
  */
@@ -1,3 +1,3 @@
1
1
  // 导出 SessionManager(业务逻辑层)
2
- export { SessionManager, sessionManager } from './SessionManager';
2
+ export { SessionManager, getSessionManager } from './SessionManager';
3
3
  export { Session } from './Session';