snow-ai 0.3.7 → 0.3.8

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 (53) hide show
  1. package/dist/agents/compactAgent.js +7 -3
  2. package/dist/agents/summaryAgent.d.ts +57 -0
  3. package/dist/agents/summaryAgent.js +259 -0
  4. package/dist/api/anthropic.d.ts +1 -0
  5. package/dist/api/anthropic.js +20 -13
  6. package/dist/api/chat.d.ts +1 -0
  7. package/dist/api/chat.js +23 -12
  8. package/dist/api/gemini.d.ts +1 -0
  9. package/dist/api/gemini.js +14 -8
  10. package/dist/api/responses.d.ts +1 -0
  11. package/dist/api/responses.js +23 -15
  12. package/dist/app.js +15 -2
  13. package/dist/hooks/useCommandHandler.js +58 -0
  14. package/dist/hooks/useCommandPanel.d.ts +2 -1
  15. package/dist/hooks/useCommandPanel.js +6 -1
  16. package/dist/hooks/useConversation.js +44 -24
  17. package/dist/hooks/useSnapshotState.d.ts +2 -0
  18. package/dist/mcp/filesystem.d.ts +131 -46
  19. package/dist/mcp/filesystem.js +188 -35
  20. package/dist/mcp/types/filesystem.types.d.ts +91 -0
  21. package/dist/mcp/utils/filesystem/batch-operations.utils.d.ts +39 -0
  22. package/dist/mcp/utils/filesystem/batch-operations.utils.js +182 -0
  23. package/dist/ui/components/ChatInput.d.ts +2 -1
  24. package/dist/ui/components/ChatInput.js +3 -3
  25. package/dist/ui/components/CommandPanel.d.ts +2 -1
  26. package/dist/ui/components/CommandPanel.js +18 -3
  27. package/dist/ui/components/MarkdownRenderer.js +10 -1
  28. package/dist/ui/components/MessageList.js +1 -1
  29. package/dist/ui/components/PendingMessages.js +1 -1
  30. package/dist/ui/components/PendingToolCalls.d.ts +11 -0
  31. package/dist/ui/components/PendingToolCalls.js +35 -0
  32. package/dist/ui/components/ToolResultPreview.d.ts +1 -1
  33. package/dist/ui/components/ToolResultPreview.js +116 -152
  34. package/dist/ui/pages/ChatScreen.d.ts +1 -0
  35. package/dist/ui/pages/ChatScreen.js +99 -60
  36. package/dist/utils/chatExporter.d.ts +9 -0
  37. package/dist/utils/chatExporter.js +126 -0
  38. package/dist/utils/commandExecutor.d.ts +1 -1
  39. package/dist/utils/commands/export.d.ts +2 -0
  40. package/dist/utils/commands/export.js +12 -0
  41. package/dist/utils/commands/init.js +3 -3
  42. package/dist/utils/fileDialog.d.ts +9 -0
  43. package/dist/utils/fileDialog.js +74 -0
  44. package/dist/utils/fileUtils.js +3 -3
  45. package/dist/utils/incrementalSnapshot.d.ts +7 -0
  46. package/dist/utils/incrementalSnapshot.js +35 -0
  47. package/dist/utils/messageFormatter.js +89 -6
  48. package/dist/utils/sessionConverter.js +11 -0
  49. package/dist/utils/sessionManager.d.ts +5 -0
  50. package/dist/utils/sessionManager.js +45 -0
  51. package/dist/utils/toolDisplayConfig.d.ts +16 -0
  52. package/dist/utils/toolDisplayConfig.js +42 -0
  53. package/package.json +1 -1
@@ -6,27 +6,110 @@ export function formatToolCallMessage(toolCall) {
6
6
  const args = JSON.parse(toolCall.function.arguments);
7
7
  const argEntries = Object.entries(args);
8
8
  const formattedArgs = [];
9
+ // Edit 工具的长内容参数列表
10
+ const editToolLongContentParams = [
11
+ 'searchContent',
12
+ 'replaceContent',
13
+ 'newContent',
14
+ 'oldContent',
15
+ 'content',
16
+ 'completeOldContent',
17
+ 'completeNewContent',
18
+ ];
19
+ // Edit 工具名称列表
20
+ const editTools = [
21
+ 'filesystem-edit',
22
+ 'filesystem-edit_search',
23
+ 'filesystem-create',
24
+ ];
25
+ const isEditTool = editTools.includes(toolCall.function.name);
9
26
  if (argEntries.length > 0) {
10
27
  argEntries.forEach(([key, value], idx, arr) => {
11
- const valueStr = typeof value === 'string'
12
- ? value.length > 60 ? `"${value.slice(0, 60)}..."` : `"${value}"`
13
- : JSON.stringify(value);
28
+ let valueStr;
29
+ // edit 工具的长内容参数进行特殊处理
30
+ if (isEditTool && editToolLongContentParams.includes(key)) {
31
+ if (typeof value === 'string') {
32
+ const lines = value.split('\n');
33
+ const lineCount = lines.length;
34
+ if (lineCount > 3) {
35
+ // 多行内容:显示行数统计
36
+ valueStr = `<${lineCount} lines>`;
37
+ }
38
+ else if (value.length > 60) {
39
+ // 单行但很长:截断显示
40
+ valueStr = `"${value.slice(0, 60)}..."`;
41
+ }
42
+ else {
43
+ // 短内容:正常显示
44
+ valueStr = `"${value}"`;
45
+ }
46
+ }
47
+ else {
48
+ valueStr = JSON.stringify(value);
49
+ }
50
+ }
51
+ else {
52
+ // 其他参数:智能处理不同类型
53
+ if (typeof value === 'string') {
54
+ // 字符串类型
55
+ valueStr =
56
+ value.length > 60 ? `"${value.slice(0, 60)}..."` : `"${value}"`;
57
+ }
58
+ else if (Array.isArray(value)) {
59
+ // 数组类型:显示元素数量
60
+ if (value.length === 0) {
61
+ valueStr = '[]';
62
+ }
63
+ else if (value.length === 1) {
64
+ // 单个元素:尝试简化显示
65
+ const item = value[0];
66
+ if (typeof item === 'object' && item !== null) {
67
+ const keys = Object.keys(item);
68
+ valueStr = `[{${keys.slice(0, 2).join(', ')}${keys.length > 2 ? ', ...' : ''}}]`;
69
+ }
70
+ else {
71
+ valueStr = JSON.stringify(value);
72
+ }
73
+ }
74
+ else {
75
+ // 多个元素:显示数量
76
+ valueStr = `<array with ${value.length} items>`;
77
+ }
78
+ }
79
+ else if (typeof value === 'object' && value !== null) {
80
+ // 对象类型:显示键名
81
+ const keys = Object.keys(value);
82
+ if (keys.length === 0) {
83
+ valueStr = '{}';
84
+ }
85
+ else if (keys.length <= 3) {
86
+ valueStr = `{${keys.join(', ')}}`;
87
+ }
88
+ else {
89
+ valueStr = `{${keys.slice(0, 3).join(', ')}, ...}`;
90
+ }
91
+ }
92
+ else {
93
+ // 其他类型(数字、布尔等)
94
+ valueStr = JSON.stringify(value);
95
+ }
96
+ }
14
97
  formattedArgs.push({
15
98
  key,
16
99
  value: valueStr,
17
- isLast: idx === arr.length - 1
100
+ isLast: idx === arr.length - 1,
18
101
  });
19
102
  });
20
103
  }
21
104
  return {
22
105
  toolName: toolCall.function.name,
23
- args: formattedArgs
106
+ args: formattedArgs,
24
107
  };
25
108
  }
26
109
  catch (e) {
27
110
  return {
28
111
  toolName: toolCall.function.name,
29
- args: []
112
+ args: [],
30
113
  };
31
114
  }
32
115
  }
@@ -161,6 +161,7 @@ export function convertSessionMessagesToUI(sessionMessages) {
161
161
  !isError) {
162
162
  try {
163
163
  const resultData = JSON.parse(msg.content);
164
+ // Handle single file edit
164
165
  if (resultData.oldContent && resultData.newContent) {
165
166
  editDiffData = {
166
167
  oldContent: resultData.oldContent,
@@ -176,6 +177,16 @@ export function convertSessionMessagesToUI(sessionMessages) {
176
177
  toolArgs.completeNewContent = resultData.completeNewContent;
177
178
  toolArgs.contextStartLine = resultData.contextStartLine;
178
179
  }
180
+ // Handle batch edit
181
+ else if (resultData.results &&
182
+ Array.isArray(resultData.results)) {
183
+ editDiffData = {
184
+ batchResults: resultData.results,
185
+ isBatch: true,
186
+ };
187
+ toolArgs.batchResults = resultData.results;
188
+ toolArgs.isBatch = true;
189
+ }
179
190
  }
180
191
  catch (e) {
181
192
  // Ignore parse errors
@@ -37,6 +37,11 @@ declare class SessionManager {
37
37
  listSessions(): Promise<SessionListItem[]>;
38
38
  private readSessionsFromDir;
39
39
  addMessage(message: ChatMessage): Promise<void>;
40
+ /**
41
+ * Generate AI-powered summary for the first conversation exchange
42
+ * This runs in the background without blocking the main flow
43
+ */
44
+ private generateAndUpdateSummary;
40
45
  getCurrentSession(): Session | null;
41
46
  setCurrentSession(session: Session): void;
42
47
  clearCurrentSession(): void;
@@ -4,6 +4,7 @@ import os from 'os';
4
4
  import { randomUUID } from 'crypto';
5
5
  import { getTodoService } from './mcpToolsManager.js';
6
6
  import { logger } from './logger.js';
7
+ import { summaryAgent } from '../agents/summaryAgent.js';
7
8
  class SessionManager {
8
9
  constructor() {
9
10
  Object.defineProperty(this, "sessionsDir", {
@@ -251,8 +252,52 @@ class SessionManager {
251
252
  this.currentSession.title = this.cleanTitle(title);
252
253
  this.currentSession.summary = this.cleanTitle(summary);
253
254
  }
255
+ // After the first complete conversation exchange (user + assistant), generate AI summary
256
+ // Only run once when messageCount becomes 2 and the second message is from assistant
257
+ if (this.currentSession.messageCount === 2 && message.role === 'assistant') {
258
+ // Run summary generation in background without blocking
259
+ this.generateAndUpdateSummary().catch(error => {
260
+ logger.error('Failed to generate conversation summary:', error);
261
+ });
262
+ }
254
263
  await this.saveSession(this.currentSession);
255
264
  }
265
+ /**
266
+ * Generate AI-powered summary for the first conversation exchange
267
+ * This runs in the background without blocking the main flow
268
+ */
269
+ async generateAndUpdateSummary() {
270
+ if (!this.currentSession || this.currentSession.messages.length < 2) {
271
+ return;
272
+ }
273
+ try {
274
+ // Extract first user and assistant messages
275
+ const firstUserMessage = this.currentSession.messages.find(m => m.role === 'user');
276
+ const firstAssistantMessage = this.currentSession.messages.find(m => m.role === 'assistant');
277
+ if (!firstUserMessage || !firstAssistantMessage) {
278
+ logger.warn('Summary agent: Could not find first user/assistant messages');
279
+ return;
280
+ }
281
+ // Generate summary using summary agent
282
+ const result = await summaryAgent.generateSummary(firstUserMessage.content, firstAssistantMessage.content);
283
+ if (result) {
284
+ // Update session with generated summary
285
+ this.currentSession.title = result.title;
286
+ this.currentSession.summary = result.summary;
287
+ // Save updated session
288
+ await this.saveSession(this.currentSession);
289
+ logger.info('Summary agent: Successfully updated session summary', {
290
+ sessionId: this.currentSession.id,
291
+ title: result.title,
292
+ summary: result.summary
293
+ });
294
+ }
295
+ }
296
+ catch (error) {
297
+ // Silently fail - don't disrupt main conversation flow
298
+ logger.error('Summary agent: Failed to generate summary', error);
299
+ }
300
+ }
256
301
  getCurrentSession() {
257
302
  return this.currentSession;
258
303
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 工具显示配置
3
+ * 用于判断哪些工具需要显示两步状态(进行中+完成),哪些工具只需要显示完成状态
4
+ */
5
+ /**
6
+ * 判断工具是否需要显示两步状态
7
+ * @param toolName - 工具名称
8
+ * @returns 是否需要显示进行中和完成两个状态
9
+ */
10
+ export declare function isToolNeedTwoStepDisplay(toolName: string): boolean;
11
+ /**
12
+ * 判断工具是否只需要在静态区显示完成状态
13
+ * @param toolName - 工具名称
14
+ * @returns 是否只需要显示完成状态
15
+ */
16
+ export declare function isToolOnlyShowCompleted(toolName: string): boolean;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * 工具显示配置
3
+ * 用于判断哪些工具需要显示两步状态(进行中+完成),哪些工具只需要显示完成状态
4
+ */
5
+ /**
6
+ * 需要显示两步状态的工具(进行中 → 完成)
7
+ * 这些通常是耗时较长的工具,用户需要看到执行进度
8
+ */
9
+ const TWO_STEP_TOOLS = new Set([
10
+ // 文件编辑工具 - 耗时较长,需要显示进度
11
+ 'filesystem-edit',
12
+ 'filesystem-edit_search',
13
+ 'filesystem-create',
14
+ // 终端执行工具 - 执行时间不确定,需要显示进度
15
+ 'terminal-execute',
16
+ // 子代理工具 - 执行复杂任务,需要显示进度
17
+ // 所有以 'subagent-' 开头的工具都需要两步显示
18
+ ]);
19
+ /**
20
+ * 判断工具是否需要显示两步状态
21
+ * @param toolName - 工具名称
22
+ * @returns 是否需要显示进行中和完成两个状态
23
+ */
24
+ export function isToolNeedTwoStepDisplay(toolName) {
25
+ // 检查是否在固定列表中
26
+ if (TWO_STEP_TOOLS.has(toolName)) {
27
+ return true;
28
+ }
29
+ // 检查是否是子代理工具 (subagent- 开头)
30
+ if (toolName.startsWith('subagent-')) {
31
+ return true;
32
+ }
33
+ return false;
34
+ }
35
+ /**
36
+ * 判断工具是否只需要在静态区显示完成状态
37
+ * @param toolName - 工具名称
38
+ * @returns 是否只需要显示完成状态
39
+ */
40
+ export function isToolOnlyShowCompleted(toolName) {
41
+ return !isToolNeedTwoStepDisplay(toolName);
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-ai",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "Intelligent Command Line Assistant powered by AI",
5
5
  "license": "MIT",
6
6
  "bin": {