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.
- package/dist/agents/compactAgent.js +7 -3
- package/dist/agents/summaryAgent.d.ts +57 -0
- package/dist/agents/summaryAgent.js +259 -0
- package/dist/api/anthropic.d.ts +1 -0
- package/dist/api/anthropic.js +20 -13
- package/dist/api/chat.d.ts +1 -0
- package/dist/api/chat.js +23 -12
- package/dist/api/gemini.d.ts +1 -0
- package/dist/api/gemini.js +14 -8
- package/dist/api/responses.d.ts +1 -0
- package/dist/api/responses.js +23 -15
- package/dist/app.js +15 -2
- package/dist/hooks/useCommandHandler.js +58 -0
- package/dist/hooks/useCommandPanel.d.ts +2 -1
- package/dist/hooks/useCommandPanel.js +6 -1
- package/dist/hooks/useConversation.js +44 -24
- package/dist/hooks/useSnapshotState.d.ts +2 -0
- package/dist/mcp/filesystem.d.ts +131 -46
- package/dist/mcp/filesystem.js +188 -35
- package/dist/mcp/types/filesystem.types.d.ts +91 -0
- package/dist/mcp/utils/filesystem/batch-operations.utils.d.ts +39 -0
- package/dist/mcp/utils/filesystem/batch-operations.utils.js +182 -0
- package/dist/ui/components/ChatInput.d.ts +2 -1
- package/dist/ui/components/ChatInput.js +3 -3
- package/dist/ui/components/CommandPanel.d.ts +2 -1
- package/dist/ui/components/CommandPanel.js +18 -3
- package/dist/ui/components/MarkdownRenderer.js +10 -1
- package/dist/ui/components/MessageList.js +1 -1
- package/dist/ui/components/PendingMessages.js +1 -1
- package/dist/ui/components/PendingToolCalls.d.ts +11 -0
- package/dist/ui/components/PendingToolCalls.js +35 -0
- package/dist/ui/components/ToolResultPreview.d.ts +1 -1
- package/dist/ui/components/ToolResultPreview.js +116 -152
- package/dist/ui/pages/ChatScreen.d.ts +1 -0
- package/dist/ui/pages/ChatScreen.js +99 -60
- package/dist/utils/chatExporter.d.ts +9 -0
- package/dist/utils/chatExporter.js +126 -0
- package/dist/utils/commandExecutor.d.ts +1 -1
- package/dist/utils/commands/export.d.ts +2 -0
- package/dist/utils/commands/export.js +12 -0
- package/dist/utils/commands/init.js +3 -3
- package/dist/utils/fileDialog.d.ts +9 -0
- package/dist/utils/fileDialog.js +74 -0
- package/dist/utils/fileUtils.js +3 -3
- package/dist/utils/incrementalSnapshot.d.ts +7 -0
- package/dist/utils/incrementalSnapshot.js +35 -0
- package/dist/utils/messageFormatter.js +89 -6
- package/dist/utils/sessionConverter.js +11 -0
- package/dist/utils/sessionManager.d.ts +5 -0
- package/dist/utils/sessionManager.js +45 -0
- package/dist/utils/toolDisplayConfig.d.ts +16 -0
- package/dist/utils/toolDisplayConfig.js +42 -0
- 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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
}
|