snow-ai 0.3.6 → 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/reviewAgent.d.ts +50 -0
- package/dist/agents/reviewAgent.js +264 -0
- package/dist/agents/summaryAgent.d.ts +34 -8
- package/dist/agents/summaryAgent.js +167 -164
- package/dist/api/anthropic.d.ts +1 -0
- package/dist/api/anthropic.js +118 -78
- package/dist/api/chat.d.ts +2 -1
- package/dist/api/chat.js +82 -52
- package/dist/api/gemini.d.ts +1 -0
- package/dist/api/gemini.js +110 -64
- package/dist/api/responses.d.ts +10 -1
- package/dist/api/responses.js +127 -79
- package/dist/api/systemPrompt.d.ts +1 -1
- package/dist/api/systemPrompt.js +36 -7
- package/dist/api/types.d.ts +8 -0
- package/dist/app.js +15 -2
- package/dist/hooks/useCommandHandler.d.ts +1 -0
- package/dist/hooks/useCommandHandler.js +102 -1
- package/dist/hooks/useCommandPanel.d.ts +2 -1
- package/dist/hooks/useCommandPanel.js +19 -1
- package/dist/hooks/useConversation.d.ts +4 -1
- package/dist/hooks/useConversation.js +91 -29
- package/dist/hooks/useKeyboardInput.js +19 -0
- package/dist/hooks/useSnapshotState.d.ts +2 -0
- package/dist/hooks/useTerminalFocus.js +13 -3
- package/dist/mcp/aceCodeSearch.d.ts +2 -76
- package/dist/mcp/aceCodeSearch.js +31 -467
- package/dist/mcp/bash.d.ts +1 -8
- package/dist/mcp/bash.js +20 -40
- package/dist/mcp/filesystem.d.ts +131 -111
- package/dist/mcp/filesystem.js +212 -375
- package/dist/mcp/ideDiagnostics.js +2 -4
- package/dist/mcp/todo.d.ts +1 -17
- package/dist/mcp/todo.js +11 -15
- package/dist/mcp/types/aceCodeSearch.types.d.ts +92 -0
- package/dist/mcp/types/aceCodeSearch.types.js +4 -0
- package/dist/mcp/types/bash.types.d.ts +13 -0
- package/dist/mcp/types/bash.types.js +4 -0
- package/dist/mcp/types/filesystem.types.d.ts +135 -0
- package/dist/mcp/types/filesystem.types.js +4 -0
- package/dist/mcp/types/todo.types.d.ts +27 -0
- package/dist/mcp/types/todo.types.js +4 -0
- package/dist/mcp/types/websearch.types.d.ts +30 -0
- package/dist/mcp/types/websearch.types.js +4 -0
- package/dist/mcp/utils/aceCodeSearch/filesystem.utils.d.ts +34 -0
- package/dist/mcp/utils/aceCodeSearch/filesystem.utils.js +146 -0
- package/dist/mcp/utils/aceCodeSearch/language.utils.d.ts +14 -0
- package/dist/mcp/utils/aceCodeSearch/language.utils.js +99 -0
- package/dist/mcp/utils/aceCodeSearch/search.utils.d.ts +31 -0
- package/dist/mcp/utils/aceCodeSearch/search.utils.js +136 -0
- package/dist/mcp/utils/aceCodeSearch/symbol.utils.d.ts +20 -0
- package/dist/mcp/utils/aceCodeSearch/symbol.utils.js +141 -0
- package/dist/mcp/utils/bash/security.utils.d.ts +20 -0
- package/dist/mcp/utils/bash/security.utils.js +34 -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/mcp/utils/filesystem/code-analysis.utils.d.ts +18 -0
- package/dist/mcp/utils/filesystem/code-analysis.utils.js +165 -0
- package/dist/mcp/utils/filesystem/match-finder.utils.d.ts +16 -0
- package/dist/mcp/utils/filesystem/match-finder.utils.js +85 -0
- package/dist/mcp/utils/filesystem/similarity.utils.d.ts +22 -0
- package/dist/mcp/utils/filesystem/similarity.utils.js +75 -0
- package/dist/mcp/utils/todo/date.utils.d.ts +9 -0
- package/dist/mcp/utils/todo/date.utils.js +14 -0
- package/dist/mcp/utils/websearch/browser.utils.d.ts +8 -0
- package/dist/mcp/utils/websearch/browser.utils.js +58 -0
- package/dist/mcp/utils/websearch/text.utils.d.ts +16 -0
- package/dist/mcp/utils/websearch/text.utils.js +39 -0
- package/dist/mcp/websearch.d.ts +1 -31
- package/dist/mcp/websearch.js +21 -97
- package/dist/ui/components/ChatInput.d.ts +3 -1
- package/dist/ui/components/ChatInput.js +12 -5
- package/dist/ui/components/CommandPanel.d.ts +2 -1
- package/dist/ui/components/CommandPanel.js +18 -3
- package/dist/ui/components/MarkdownRenderer.d.ts +1 -2
- package/dist/ui/components/MarkdownRenderer.js +25 -153
- package/dist/ui/components/MessageList.js +5 -5
- 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/SessionListScreen.js +37 -17
- package/dist/ui/components/ToolResultPreview.d.ts +1 -1
- package/dist/ui/components/ToolResultPreview.js +119 -155
- package/dist/ui/components/UsagePanel.d.ts +2 -0
- package/dist/ui/components/UsagePanel.js +360 -0
- package/dist/ui/pages/ChatScreen.d.ts +5 -0
- package/dist/ui/pages/ChatScreen.js +164 -85
- package/dist/ui/pages/ConfigScreen.js +23 -19
- package/dist/ui/pages/HeadlessModeScreen.js +2 -4
- package/dist/ui/pages/SubAgentConfigScreen.js +17 -17
- package/dist/ui/pages/SystemPromptConfigScreen.js +7 -6
- package/dist/utils/chatExporter.d.ts +9 -0
- package/dist/utils/chatExporter.js +126 -0
- package/dist/utils/commandExecutor.d.ts +3 -3
- package/dist/utils/commandExecutor.js +4 -4
- package/dist/utils/commands/export.d.ts +2 -0
- package/dist/utils/commands/export.js +12 -0
- package/dist/utils/commands/home.d.ts +2 -0
- package/dist/utils/commands/home.js +12 -0
- package/dist/utils/commands/init.js +3 -3
- package/dist/utils/commands/review.d.ts +2 -0
- package/dist/utils/commands/review.js +81 -0
- package/dist/utils/commands/role.d.ts +2 -0
- package/dist/utils/commands/role.js +37 -0
- package/dist/utils/commands/usage.d.ts +2 -0
- package/dist/utils/commands/usage.js +12 -0
- package/dist/utils/contextCompressor.js +99 -367
- package/dist/utils/fileDialog.d.ts +9 -0
- package/dist/utils/fileDialog.js +74 -0
- package/dist/utils/incrementalSnapshot.d.ts +7 -0
- package/dist/utils/incrementalSnapshot.js +35 -0
- package/dist/utils/mcpToolsManager.js +12 -12
- package/dist/utils/messageFormatter.js +89 -6
- package/dist/utils/proxyUtils.d.ts +15 -0
- package/dist/utils/proxyUtils.js +50 -0
- package/dist/utils/retryUtils.d.ts +27 -0
- package/dist/utils/retryUtils.js +114 -2
- package/dist/utils/sessionConverter.js +11 -0
- package/dist/utils/sessionManager.d.ts +7 -5
- package/dist/utils/sessionManager.js +60 -82
- package/dist/utils/terminal.js +4 -3
- package/dist/utils/toolDisplayConfig.d.ts +16 -0
- package/dist/utils/toolDisplayConfig.js +42 -0
- package/dist/utils/usageLogger.d.ts +11 -0
- package/dist/utils/usageLogger.js +99 -0
- package/package.json +3 -7
|
@@ -8,6 +8,7 @@ import PendingMessages from '../components/PendingMessages.js';
|
|
|
8
8
|
import MCPInfoScreen from '../components/MCPInfoScreen.js';
|
|
9
9
|
import MCPInfoPanel from '../components/MCPInfoPanel.js';
|
|
10
10
|
import SessionListPanel from '../components/SessionListPanel.js';
|
|
11
|
+
import UsagePanel from '../components/UsagePanel.js';
|
|
11
12
|
import MarkdownRenderer from '../components/MarkdownRenderer.js';
|
|
12
13
|
import ToolConfirmation from '../components/ToolConfirmation.js';
|
|
13
14
|
import DiffViewer from '../components/DiffViewer.js';
|
|
@@ -38,6 +39,11 @@ import '../../utils/commands/yolo.js';
|
|
|
38
39
|
import '../../utils/commands/init.js';
|
|
39
40
|
import '../../utils/commands/ide.js';
|
|
40
41
|
import '../../utils/commands/compact.js';
|
|
42
|
+
import '../../utils/commands/home.js';
|
|
43
|
+
import '../../utils/commands/review.js';
|
|
44
|
+
import '../../utils/commands/role.js';
|
|
45
|
+
import '../../utils/commands/usage.js';
|
|
46
|
+
import '../../utils/commands/export.js';
|
|
41
47
|
export default function ChatScreen({ skipWelcome }) {
|
|
42
48
|
const [messages, setMessages] = useState([]);
|
|
43
49
|
const [isSaving] = useState(false);
|
|
@@ -62,7 +68,9 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
62
68
|
const [compressionError, setCompressionError] = useState(null);
|
|
63
69
|
const [showSessionPanel, setShowSessionPanel] = useState(false);
|
|
64
70
|
const [showMcpPanel, setShowMcpPanel] = useState(false);
|
|
71
|
+
const [showUsagePanel, setShowUsagePanel] = useState(false);
|
|
65
72
|
const [shouldIncludeSystemInfo, setShouldIncludeSystemInfo] = useState(true); // Include on first message
|
|
73
|
+
const [restoreInputContent, setRestoreInputContent] = useState(null);
|
|
66
74
|
const { columns: terminalWidth, rows: terminalHeight } = useTerminalSize();
|
|
67
75
|
const { stdout } = useStdout();
|
|
68
76
|
const workingDirectory = process.cwd();
|
|
@@ -86,6 +94,17 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
86
94
|
// Ignore localStorage errors
|
|
87
95
|
}
|
|
88
96
|
}, [yoloMode]);
|
|
97
|
+
// Clear restore input content after it's been used
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (restoreInputContent !== null) {
|
|
100
|
+
// Clear after a short delay to ensure ChatInput has processed it
|
|
101
|
+
const timer = setTimeout(() => {
|
|
102
|
+
setRestoreInputContent(null);
|
|
103
|
+
}, 100);
|
|
104
|
+
return () => clearTimeout(timer);
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}, [restoreInputContent]);
|
|
89
108
|
// Auto-resume last session when skipWelcome is true
|
|
90
109
|
useEffect(() => {
|
|
91
110
|
if (!skipWelcome)
|
|
@@ -161,6 +180,7 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
161
180
|
setShowSessionPanel,
|
|
162
181
|
setShowMcpInfo,
|
|
163
182
|
setShowMcpPanel,
|
|
183
|
+
setShowUsagePanel,
|
|
164
184
|
setMcpPanelKey,
|
|
165
185
|
setYoloMode,
|
|
166
186
|
setContextUsage: streamingState.setContextUsage,
|
|
@@ -224,6 +244,12 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
224
244
|
}
|
|
225
245
|
return;
|
|
226
246
|
}
|
|
247
|
+
if (showUsagePanel) {
|
|
248
|
+
if (key.escape) {
|
|
249
|
+
setShowUsagePanel(false);
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
227
253
|
if (showMcpInfo) {
|
|
228
254
|
if (key.escape) {
|
|
229
255
|
setShowMcpInfo(false);
|
|
@@ -235,6 +261,8 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
235
261
|
streamingState.abortController) {
|
|
236
262
|
// Abort the controller
|
|
237
263
|
streamingState.abortController.abort();
|
|
264
|
+
// Clear retry status immediately when user cancels
|
|
265
|
+
streamingState.setRetryStatus(null);
|
|
238
266
|
// Remove all pending tool call messages (those with toolPending: true)
|
|
239
267
|
setMessages(prev => prev.filter(msg => !msg.toolPending));
|
|
240
268
|
// Add discontinued message
|
|
@@ -253,7 +281,7 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
253
281
|
streamingState.setStreamTokenCount(0);
|
|
254
282
|
}
|
|
255
283
|
});
|
|
256
|
-
const handleHistorySelect = async (selectedIndex,
|
|
284
|
+
const handleHistorySelect = async (selectedIndex, message) => {
|
|
257
285
|
// Count total files that will be rolled back (from selectedIndex onwards)
|
|
258
286
|
let totalFileCount = 0;
|
|
259
287
|
for (const [index, count] of snapshotState.snapshotFileCount.entries()) {
|
|
@@ -272,39 +300,95 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
272
300
|
messageIndex: selectedIndex,
|
|
273
301
|
fileCount: filePaths.length, // Use actual unique file count
|
|
274
302
|
filePaths,
|
|
303
|
+
message, // Save message for restore after rollback
|
|
275
304
|
});
|
|
276
305
|
}
|
|
277
306
|
else {
|
|
278
307
|
// No files to rollback, just rollback conversation
|
|
279
|
-
|
|
308
|
+
// Note: message is already in input buffer via useHistoryNavigation
|
|
309
|
+
await performRollback(selectedIndex, false);
|
|
280
310
|
}
|
|
281
311
|
};
|
|
282
312
|
const performRollback = async (selectedIndex, rollbackFiles) => {
|
|
313
|
+
const currentSession = sessionManager.getCurrentSession();
|
|
283
314
|
// Rollback workspace to checkpoint if requested
|
|
284
|
-
if (rollbackFiles) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
315
|
+
if (rollbackFiles && currentSession) {
|
|
316
|
+
// Use rollbackToMessageIndex to rollback all snapshots >= selectedIndex
|
|
317
|
+
await incrementalSnapshotManager.rollbackToMessageIndex(currentSession.id, selectedIndex);
|
|
318
|
+
}
|
|
319
|
+
// For session file: find the correct truncation point based on session messages
|
|
320
|
+
// We need to truncate to the same user message in the session file
|
|
321
|
+
if (currentSession) {
|
|
322
|
+
// Count how many user messages we're deleting (from selectedIndex onwards in UI)
|
|
323
|
+
const uiUserMessagesToDelete = messages
|
|
324
|
+
.slice(selectedIndex)
|
|
325
|
+
.filter(msg => msg.role === 'user').length;
|
|
326
|
+
// Find the corresponding user message in session to delete
|
|
327
|
+
// We start from the end and count backwards
|
|
328
|
+
let sessionUserMessageCount = 0;
|
|
329
|
+
let sessionTruncateIndex = currentSession.messages.length;
|
|
330
|
+
for (let i = currentSession.messages.length - 1; i >= 0; i--) {
|
|
331
|
+
const msg = currentSession.messages[i];
|
|
332
|
+
if (msg && msg.role === 'user') {
|
|
333
|
+
sessionUserMessageCount++;
|
|
334
|
+
if (sessionUserMessageCount === uiUserMessagesToDelete) {
|
|
335
|
+
// We want to delete from this user message onwards
|
|
336
|
+
sessionTruncateIndex = i;
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// Special case: rolling back to index 0 means deleting the entire session
|
|
342
|
+
if (sessionTruncateIndex === 0 && currentSession) {
|
|
343
|
+
// Delete all snapshots for this session
|
|
344
|
+
await incrementalSnapshotManager.clearAllSnapshots(currentSession.id);
|
|
345
|
+
// Delete the session file
|
|
346
|
+
await sessionManager.deleteSession(currentSession.id);
|
|
347
|
+
// Clear current session
|
|
348
|
+
sessionManager.clearCurrentSession();
|
|
349
|
+
// Clear all messages
|
|
350
|
+
setMessages([]);
|
|
351
|
+
// Clear saved messages
|
|
352
|
+
clearSavedMessages();
|
|
353
|
+
// Clear snapshot state
|
|
354
|
+
snapshotState.setSnapshotFileCount(new Map());
|
|
355
|
+
// Clear pending rollback dialog
|
|
356
|
+
snapshotState.setPendingRollback(null);
|
|
357
|
+
// Trigger remount
|
|
358
|
+
setRemountKey(prev => prev + 1);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
// Delete snapshot files >= selectedIndex (regardless of whether files were rolled back)
|
|
362
|
+
await incrementalSnapshotManager.deleteSnapshotsFromIndex(currentSession.id, selectedIndex);
|
|
363
|
+
// Reload snapshot file counts from disk after deletion
|
|
364
|
+
const snapshots = await incrementalSnapshotManager.listSnapshots(currentSession.id);
|
|
365
|
+
const counts = new Map();
|
|
366
|
+
for (const snapshot of snapshots) {
|
|
367
|
+
counts.set(snapshot.messageIndex, snapshot.fileCount);
|
|
289
368
|
}
|
|
369
|
+
snapshotState.setSnapshotFileCount(counts);
|
|
370
|
+
// Truncate session messages
|
|
371
|
+
await sessionManager.truncateMessages(sessionTruncateIndex);
|
|
290
372
|
}
|
|
291
|
-
// Truncate messages array to remove the selected user message and everything after it
|
|
373
|
+
// Truncate UI messages array to remove the selected user message and everything after it
|
|
292
374
|
setMessages(prev => prev.slice(0, selectedIndex));
|
|
293
|
-
// Truncate session messages to match the UI state
|
|
294
|
-
await sessionManager.truncateMessages(selectedIndex);
|
|
295
375
|
clearSavedMessages();
|
|
296
376
|
setRemountKey(prev => prev + 1);
|
|
297
377
|
// Clear pending rollback dialog
|
|
298
378
|
snapshotState.setPendingRollback(null);
|
|
299
379
|
};
|
|
300
|
-
const handleRollbackConfirm = (rollbackFiles) => {
|
|
380
|
+
const handleRollbackConfirm = async (rollbackFiles) => {
|
|
301
381
|
if (rollbackFiles === null) {
|
|
302
382
|
// User cancelled - just close the dialog without doing anything
|
|
303
383
|
snapshotState.setPendingRollback(null);
|
|
304
384
|
return;
|
|
305
385
|
}
|
|
306
386
|
if (snapshotState.pendingRollback) {
|
|
307
|
-
|
|
387
|
+
// Restore message to input before rollback
|
|
388
|
+
if (snapshotState.pendingRollback.message) {
|
|
389
|
+
setRestoreInputContent(snapshotState.pendingRollback.message);
|
|
390
|
+
}
|
|
391
|
+
await performRollback(snapshotState.pendingRollback.messageIndex, rollbackFiles);
|
|
308
392
|
}
|
|
309
393
|
};
|
|
310
394
|
const handleSessionPanelSelect = async (sessionId) => {
|
|
@@ -351,6 +435,8 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
351
435
|
await processMessage(message, images);
|
|
352
436
|
};
|
|
353
437
|
const processMessage = async (message, images, useBasicModel, hideUserMessage) => {
|
|
438
|
+
// Clear any previous retry status when starting a new request
|
|
439
|
+
streamingState.setRetryStatus(null);
|
|
354
440
|
// Parse and validate file references
|
|
355
441
|
const { cleanContent, validFiles } = await parseAndValidateFileReferences(message);
|
|
356
442
|
// Separate image files from regular files
|
|
@@ -418,15 +504,18 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
418
504
|
}
|
|
419
505
|
catch (error) {
|
|
420
506
|
if (controller.signal.aborted) {
|
|
421
|
-
return
|
|
507
|
+
// Don't return here - let finally block execute
|
|
508
|
+
// Just skip error display for aborted requests
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
512
|
+
const finalMessage = {
|
|
513
|
+
role: 'assistant',
|
|
514
|
+
content: `Error: ${errorMessage}`,
|
|
515
|
+
streaming: false,
|
|
516
|
+
};
|
|
517
|
+
setMessages(prev => [...prev, finalMessage]);
|
|
422
518
|
}
|
|
423
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
424
|
-
const finalMessage = {
|
|
425
|
-
role: 'assistant',
|
|
426
|
-
content: `Error: ${errorMessage}`,
|
|
427
|
-
streaming: false,
|
|
428
|
-
};
|
|
429
|
-
setMessages(prev => [...prev, finalMessage]);
|
|
430
519
|
}
|
|
431
520
|
finally {
|
|
432
521
|
// End streaming
|
|
@@ -440,6 +529,8 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
440
529
|
const processPendingMessages = async () => {
|
|
441
530
|
if (pendingMessages.length === 0)
|
|
442
531
|
return;
|
|
532
|
+
// Clear any previous retry status when starting a new request
|
|
533
|
+
streamingState.setRetryStatus(null);
|
|
443
534
|
// Get current pending messages and clear them immediately
|
|
444
535
|
const messagesToProcess = [...pendingMessages];
|
|
445
536
|
setPendingMessages([]);
|
|
@@ -500,15 +591,18 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
500
591
|
}
|
|
501
592
|
catch (error) {
|
|
502
593
|
if (controller.signal.aborted) {
|
|
503
|
-
return
|
|
594
|
+
// Don't return here - let finally block execute
|
|
595
|
+
// Just skip error display for aborted requests
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
599
|
+
const finalMessage = {
|
|
600
|
+
role: 'assistant',
|
|
601
|
+
content: `Error: ${errorMessage}`,
|
|
602
|
+
streaming: false,
|
|
603
|
+
};
|
|
604
|
+
setMessages(prev => [...prev, finalMessage]);
|
|
504
605
|
}
|
|
505
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
506
|
-
const finalMessage = {
|
|
507
|
-
role: 'assistant',
|
|
508
|
-
content: `Error: ${errorMessage}`,
|
|
509
|
-
streaming: false,
|
|
510
|
-
};
|
|
511
|
-
setMessages(prev => [...prev, finalMessage]);
|
|
512
606
|
}
|
|
513
607
|
finally {
|
|
514
608
|
// End streaming
|
|
@@ -553,10 +647,11 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
553
647
|
workingDirectory)))),
|
|
554
648
|
...messages
|
|
555
649
|
.filter(m => !m.streaming)
|
|
556
|
-
.map((message, index) => {
|
|
650
|
+
.map((message, index, filteredMessages) => {
|
|
557
651
|
// Determine tool message type and color
|
|
558
652
|
let toolStatusColor = 'cyan';
|
|
559
653
|
let isToolMessage = false;
|
|
654
|
+
const isLastMessage = index === filteredMessages.length - 1;
|
|
560
655
|
if (message.role === 'assistant') {
|
|
561
656
|
if (message.content.startsWith('⚡')) {
|
|
562
657
|
isToolMessage = true;
|
|
@@ -574,7 +669,7 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
574
669
|
toolStatusColor = 'blue';
|
|
575
670
|
}
|
|
576
671
|
}
|
|
577
|
-
return (React.createElement(Box, { key: `msg-${index}`,
|
|
672
|
+
return (React.createElement(Box, { key: `msg-${index}`, marginTop: index > 0 ? 1 : 0, marginBottom: isLastMessage ? 1 : 0, paddingX: 1, flexDirection: "column", width: terminalWidth },
|
|
578
673
|
React.createElement(Box, null,
|
|
579
674
|
React.createElement(Text, { color: message.role === 'user'
|
|
580
675
|
? 'green'
|
|
@@ -585,18 +680,18 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
585
680
|
: message.role === 'command'
|
|
586
681
|
? '⌘'
|
|
587
682
|
: '❆'),
|
|
588
|
-
React.createElement(Box, { marginLeft: 1,
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
683
|
+
React.createElement(Box, { marginLeft: 1, flexDirection: "column" }, message.role === 'command' ? (React.createElement(React.Fragment, null,
|
|
684
|
+
React.createElement(Text, { color: "gray", dimColor: true },
|
|
685
|
+
"\u2514\u2500 ",
|
|
686
|
+
message.commandName),
|
|
687
|
+
message.content && (React.createElement(Text, { color: "white" }, message.content)))) : message.showTodoTree ? (React.createElement(TodoTree, { todos: currentTodos })) : (React.createElement(React.Fragment, null,
|
|
688
|
+
message.role === 'user' || isToolMessage ? (React.createElement(Text, { color: message.role === 'user'
|
|
592
689
|
? 'gray'
|
|
593
|
-
:
|
|
594
|
-
?
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
: 'red'
|
|
599
|
-
: undefined }),
|
|
690
|
+
: message.content.startsWith('⚡')
|
|
691
|
+
? 'yellow'
|
|
692
|
+
: message.content.startsWith('✓')
|
|
693
|
+
? 'green'
|
|
694
|
+
: 'red' }, message.content || ' ')) : (React.createElement(MarkdownRenderer, { content: message.content || ' ' })),
|
|
600
695
|
message.toolDisplay &&
|
|
601
696
|
message.toolDisplay.args.length > 0 && (React.createElement(Box, { flexDirection: "column" }, message.toolDisplay.args.map((arg, argIndex) => (React.createElement(Text, { key: argIndex, color: "gray", dimColor: true },
|
|
602
697
|
arg.isLast ? '└─' : '├─',
|
|
@@ -606,8 +701,7 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
606
701
|
' ',
|
|
607
702
|
arg.value))))),
|
|
608
703
|
message.toolCall &&
|
|
609
|
-
|
|
610
|
-
message.toolCall.name === 'filesystem-write') &&
|
|
704
|
+
message.toolCall.name === 'filesystem-create' &&
|
|
611
705
|
message.toolCall.arguments.content && (React.createElement(Box, { marginTop: 1 },
|
|
612
706
|
React.createElement(DiffViewer, { newContent: message.toolCall.arguments.content, filename: message.toolCall.arguments.path }))),
|
|
613
707
|
message.toolCall &&
|
|
@@ -626,47 +720,27 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
626
720
|
.completeOldContent, completeNewContent: message.toolCall.arguments
|
|
627
721
|
.completeNewContent, startLineNumber: message.toolCall.arguments.contextStartLine }))),
|
|
628
722
|
message.toolCall &&
|
|
629
|
-
message.toolCall.name === '
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
React.createElement(
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
React.createElement(Text, { color: "green", dimColor: true }, "\u2514\u2500 stdout:"),
|
|
645
|
-
React.createElement(Box, { paddingLeft: 2 },
|
|
646
|
-
React.createElement(Text, { color: "white" }, message.toolCall.arguments.stdout
|
|
647
|
-
.trim()
|
|
648
|
-
.split('\n')
|
|
649
|
-
.slice(0, 20)
|
|
650
|
-
.join('\n')),
|
|
651
|
-
message.toolCall.arguments.stdout
|
|
652
|
-
.trim()
|
|
653
|
-
.split('\n').length > 20 && (React.createElement(Text, { color: "gray", dimColor: true }, "... (output truncated)"))))),
|
|
654
|
-
message.toolCall.arguments.stderr &&
|
|
655
|
-
message.toolCall.arguments.stderr.trim()
|
|
656
|
-
.length > 0 && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
657
|
-
React.createElement(Text, { color: "red", dimColor: true }, "\u2514\u2500 stderr:"),
|
|
658
|
-
React.createElement(Box, { paddingLeft: 2 },
|
|
659
|
-
React.createElement(Text, { color: "red" }, message.toolCall.arguments.stderr
|
|
660
|
-
.trim()
|
|
661
|
-
.split('\n')
|
|
662
|
-
.slice(0, 10)
|
|
663
|
-
.join('\n')),
|
|
664
|
-
message.toolCall.arguments.stderr
|
|
665
|
-
.trim()
|
|
666
|
-
.split('\n').length > 10 && (React.createElement(Text, { color: "gray", dimColor: true }, "... (output truncated)"))))))),
|
|
723
|
+
(message.toolCall.name === 'filesystem-edit' ||
|
|
724
|
+
message.toolCall.name ===
|
|
725
|
+
'filesystem-edit_search') &&
|
|
726
|
+
message.toolCall.arguments.isBatch &&
|
|
727
|
+
message.toolCall.arguments.batchResults &&
|
|
728
|
+
Array.isArray(message.toolCall.arguments.batchResults) && (React.createElement(Box, { marginTop: 1, flexDirection: "column" }, message.toolCall.arguments.batchResults.map((fileResult, index) => {
|
|
729
|
+
if (fileResult.success &&
|
|
730
|
+
fileResult.oldContent &&
|
|
731
|
+
fileResult.newContent) {
|
|
732
|
+
return (React.createElement(Box, { key: index, flexDirection: "column", marginBottom: 1 },
|
|
733
|
+
React.createElement(Text, { bold: true, color: "cyan" }, `File ${index + 1}: ${fileResult.path}`),
|
|
734
|
+
React.createElement(DiffViewer, { oldContent: fileResult.oldContent, newContent: fileResult.newContent, filename: fileResult.path, completeOldContent: fileResult.completeOldContent, completeNewContent: fileResult.completeNewContent, startLineNumber: fileResult.contextStartLine })));
|
|
735
|
+
}
|
|
736
|
+
return null;
|
|
737
|
+
}))),
|
|
667
738
|
message.content.startsWith('✓') &&
|
|
668
739
|
message.toolResult &&
|
|
669
|
-
|
|
740
|
+
// 只在没有 diff 数据时显示预览(有 diff 的工具会用 DiffViewer 显示)
|
|
741
|
+
!(message.toolCall &&
|
|
742
|
+
(message.toolCall.arguments?.oldContent ||
|
|
743
|
+
message.toolCall.arguments?.batchResults)) && (React.createElement(ToolResultPreview, { toolName: message.content
|
|
670
744
|
.replace('✓ ', '')
|
|
671
745
|
.split('\n')[0] || '', result: message.toolResult, maxLines: 5 })),
|
|
672
746
|
message.role === 'user' && message.systemInfo && (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
@@ -747,13 +821,18 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
747
821
|
React.createElement(MCPInfoPanel, null),
|
|
748
822
|
React.createElement(Box, { marginTop: 1 },
|
|
749
823
|
React.createElement(Text, { color: "gray", dimColor: true }, "Press ESC to close")))),
|
|
824
|
+
showUsagePanel && (React.createElement(Box, { paddingX: 1, flexDirection: "column", width: terminalWidth },
|
|
825
|
+
React.createElement(UsagePanel, null),
|
|
826
|
+
React.createElement(Box, { marginTop: 1 },
|
|
827
|
+
React.createElement(Text, { color: "gray", dimColor: true }, "Press ESC to close")))),
|
|
750
828
|
snapshotState.pendingRollback && (React.createElement(FileRollbackConfirmation, { fileCount: snapshotState.pendingRollback.fileCount, filePaths: snapshotState.pendingRollback.filePaths || [], onConfirm: handleRollbackConfirm })),
|
|
751
829
|
!pendingToolConfirmation &&
|
|
752
830
|
!isCompressing &&
|
|
753
831
|
!showSessionPanel &&
|
|
754
832
|
!showMcpPanel &&
|
|
833
|
+
!showUsagePanel &&
|
|
755
834
|
!snapshotState.pendingRollback && (React.createElement(React.Fragment, null,
|
|
756
|
-
React.createElement(ChatInput, { onSubmit: handleMessageSubmit, onCommand: handleCommandExecution, placeholder: "Ask me anything about coding...", disabled: !!pendingToolConfirmation, chatHistory: messages, onHistorySelect: handleHistorySelect, yoloMode: yoloMode, contextUsage: streamingState.contextUsage
|
|
835
|
+
React.createElement(ChatInput, { onSubmit: handleMessageSubmit, onCommand: handleCommandExecution, placeholder: "Ask me anything about coding...", disabled: !!pendingToolConfirmation, isProcessing: streamingState.isStreaming || isSaving, chatHistory: messages, onHistorySelect: handleHistorySelect, yoloMode: yoloMode, contextUsage: streamingState.contextUsage
|
|
757
836
|
? {
|
|
758
837
|
inputTokens: streamingState.contextUsage.prompt_tokens,
|
|
759
838
|
maxContextTokens: getOpenAiConfig().maxContextTokens || 4000,
|
|
@@ -761,8 +840,8 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
761
840
|
cacheReadTokens: streamingState.contextUsage.cache_read_input_tokens,
|
|
762
841
|
cachedTokens: streamingState.contextUsage.cached_tokens,
|
|
763
842
|
}
|
|
764
|
-
: undefined }),
|
|
765
|
-
vscodeState.vscodeConnectionStatus !== 'disconnected' && (React.createElement(Box, { marginTop: 1 },
|
|
843
|
+
: undefined, initialContent: restoreInputContent }),
|
|
844
|
+
vscodeState.vscodeConnectionStatus !== 'disconnected' && (React.createElement(Box, { marginTop: 1, paddingX: 1 },
|
|
766
845
|
React.createElement(Text, { color: vscodeState.vscodeConnectionStatus === 'connecting'
|
|
767
846
|
? 'yellow'
|
|
768
847
|
: vscodeState.vscodeConnectionStatus === 'connected'
|
|
@@ -294,25 +294,6 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
294
294
|
if (isFocusEventInput(rawInput)) {
|
|
295
295
|
return;
|
|
296
296
|
}
|
|
297
|
-
// Handle profile shortcuts (highest priority - works even in Select mode)
|
|
298
|
-
if (currentField === 'profile' && (input === 'n' || input === 'N')) {
|
|
299
|
-
// Handle profile creation (works in both normal and editing mode)
|
|
300
|
-
setProfileMode('creating');
|
|
301
|
-
setNewProfileName('');
|
|
302
|
-
setIsEditing(false); // Exit Select editing mode
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
if (currentField === 'profile' && (input === 'd' || input === 'D')) {
|
|
306
|
-
// Handle profile deletion (works in both normal and editing mode)
|
|
307
|
-
if (activeProfile === 'default') {
|
|
308
|
-
setErrors(['Cannot delete the default profile']);
|
|
309
|
-
setIsEditing(false);
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
setProfileMode('deleting');
|
|
313
|
-
setIsEditing(false); // Exit Select editing mode
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
297
|
// Handle profile creation mode
|
|
317
298
|
if (profileMode === 'creating') {
|
|
318
299
|
if (key.return) {
|
|
@@ -336,6 +317,29 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
336
317
|
}
|
|
337
318
|
return;
|
|
338
319
|
}
|
|
320
|
+
// Handle profile shortcuts (only when in normal profile mode)
|
|
321
|
+
if (profileMode === 'normal' &&
|
|
322
|
+
currentField === 'profile' &&
|
|
323
|
+
(input === 'n' || input === 'N')) {
|
|
324
|
+
// Handle profile creation (works in both normal and editing mode)
|
|
325
|
+
setProfileMode('creating');
|
|
326
|
+
setNewProfileName('');
|
|
327
|
+
setIsEditing(false); // Exit Select editing mode
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (profileMode === 'normal' &&
|
|
331
|
+
currentField === 'profile' &&
|
|
332
|
+
(input === 'd' || input === 'D')) {
|
|
333
|
+
// Handle profile deletion (works in both normal and editing mode)
|
|
334
|
+
if (activeProfile === 'default') {
|
|
335
|
+
setErrors(['Cannot delete the default profile']);
|
|
336
|
+
setIsEditing(false);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
setProfileMode('deleting');
|
|
340
|
+
setIsEditing(false); // Exit Select editing mode
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
339
343
|
// Handle loading state
|
|
340
344
|
if (loading) {
|
|
341
345
|
if (key.escape) {
|
|
@@ -6,6 +6,7 @@ import { handleConversationWithTools } from '../../hooks/useConversation.js';
|
|
|
6
6
|
import { useStreamingState } from '../../hooks/useStreamingState.js';
|
|
7
7
|
import { useToolConfirmation } from '../../hooks/useToolConfirmation.js';
|
|
8
8
|
import { useVSCodeState } from '../../hooks/useVSCodeState.js';
|
|
9
|
+
import { useSessionSave } from '../../hooks/useSessionSave.js';
|
|
9
10
|
import { parseAndValidateFileReferences, createMessageWithFileInstructions, getSystemInfo, } from '../../utils/fileUtils.js';
|
|
10
11
|
// Console-based markdown renderer functions
|
|
11
12
|
function renderConsoleMarkdown(content) {
|
|
@@ -202,6 +203,7 @@ export default function HeadlessModeScreen({ prompt, onComplete }) {
|
|
|
202
203
|
// Use custom hooks
|
|
203
204
|
const streamingState = useStreamingState();
|
|
204
205
|
const vscodeState = useVSCodeState();
|
|
206
|
+
const { saveMessage } = useSessionSave();
|
|
205
207
|
// Use tool confirmation hook
|
|
206
208
|
const { requestToolConfirmation, isToolAutoApproved, addMultipleToAlwaysApproved, } = useToolConfirmation();
|
|
207
209
|
// Listen for message changes to display AI responses and tool calls
|
|
@@ -335,10 +337,6 @@ export default function HeadlessModeScreen({ prompt, onComplete }) {
|
|
|
335
337
|
console.log(`\x1b[36m└─ Assistant Response\x1b[0m`);
|
|
336
338
|
// Create message for AI
|
|
337
339
|
const messageForAI = createMessageWithFileInstructions(cleanContent, regularFiles, systemInfo, vscodeState.vscodeConnected ? vscodeState.editorContext : undefined);
|
|
338
|
-
// Custom save message function for headless mode
|
|
339
|
-
const saveMessage = async () => {
|
|
340
|
-
// In headless mode, we don't need to save messages
|
|
341
|
-
};
|
|
342
340
|
// Start conversation with tool support
|
|
343
341
|
await handleConversationWithTools({
|
|
344
342
|
userContent: messageForAI,
|
|
@@ -8,30 +8,30 @@ const toolCategories = [
|
|
|
8
8
|
{
|
|
9
9
|
name: 'Filesystem Tools',
|
|
10
10
|
tools: [
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
11
|
+
'filesystem-read',
|
|
12
|
+
'filesystem-create',
|
|
13
|
+
'filesystem-edit',
|
|
14
|
+
'filesystem-edit_search',
|
|
15
|
+
'filesystem-delete',
|
|
16
|
+
'filesystem-list',
|
|
17
17
|
],
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
name: 'ACE Code Search Tools',
|
|
21
21
|
tools: [
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
22
|
+
'ace-search_symbols',
|
|
23
|
+
'ace-find_definition',
|
|
24
|
+
'ace-find_references',
|
|
25
|
+
'ace-semantic_search',
|
|
26
|
+
'ace-text_search',
|
|
27
|
+
'ace-file_outline',
|
|
28
|
+
'ace-index_stats',
|
|
29
|
+
'ace-clear_cache',
|
|
30
30
|
],
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
name: 'Terminal Tools',
|
|
34
|
-
tools: ['
|
|
34
|
+
tools: ['terminal-execute'],
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
name: 'TODO Management Tools',
|
|
@@ -45,11 +45,11 @@ const toolCategories = [
|
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
name: 'Web Search Tools',
|
|
48
|
-
tools: ['
|
|
48
|
+
tools: ['websearch-search', 'websearch-fetch'],
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
name: 'IDE Diagnostics Tools',
|
|
52
|
-
tools: ['
|
|
52
|
+
tools: ['ide-get_diagnostics'],
|
|
53
53
|
},
|
|
54
54
|
];
|
|
55
55
|
export default function SubAgentConfigScreen({ onBack, onSave, inlineMode = false, agentId, }) {
|
|
@@ -4,7 +4,7 @@ import { spawn } from 'child_process';
|
|
|
4
4
|
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { homedir, platform } from 'os';
|
|
7
|
-
import {
|
|
7
|
+
import { getSystemPrompt } from '../../api/systemPrompt.js';
|
|
8
8
|
const CONFIG_DIR = join(homedir(), '.snow');
|
|
9
9
|
const SYSTEM_PROMPT_FILE = join(CONFIG_DIR, 'system-prompt.txt');
|
|
10
10
|
function getSystemEditor() {
|
|
@@ -24,14 +24,14 @@ export default function SystemPromptConfigScreen({ onBack }) {
|
|
|
24
24
|
const openEditor = async () => {
|
|
25
25
|
ensureConfigDirectory();
|
|
26
26
|
// 读取系统提示词文件,如果不存在则使用默认系统提示词
|
|
27
|
-
let currentPrompt =
|
|
27
|
+
let currentPrompt = getSystemPrompt();
|
|
28
28
|
if (existsSync(SYSTEM_PROMPT_FILE)) {
|
|
29
29
|
try {
|
|
30
30
|
currentPrompt = readFileSync(SYSTEM_PROMPT_FILE, 'utf8');
|
|
31
31
|
}
|
|
32
32
|
catch {
|
|
33
33
|
// 读取失败,使用默认
|
|
34
|
-
currentPrompt =
|
|
34
|
+
currentPrompt = getSystemPrompt();
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
// 写入临时文件供编辑
|
|
@@ -39,7 +39,7 @@ export default function SystemPromptConfigScreen({ onBack }) {
|
|
|
39
39
|
const editor = getSystemEditor();
|
|
40
40
|
exit();
|
|
41
41
|
const child = spawn(editor, [SYSTEM_PROMPT_FILE], {
|
|
42
|
-
stdio: 'inherit'
|
|
42
|
+
stdio: 'inherit',
|
|
43
43
|
});
|
|
44
44
|
child.on('close', () => {
|
|
45
45
|
// 读取编辑后的内容
|
|
@@ -47,7 +47,8 @@ export default function SystemPromptConfigScreen({ onBack }) {
|
|
|
47
47
|
try {
|
|
48
48
|
const editedContent = readFileSync(SYSTEM_PROMPT_FILE, 'utf8');
|
|
49
49
|
const trimmedContent = editedContent.trim();
|
|
50
|
-
if (trimmedContent === '' ||
|
|
50
|
+
if (trimmedContent === '' ||
|
|
51
|
+
trimmedContent === getSystemPrompt().trim()) {
|
|
51
52
|
// 内容为空或与默认相同,删除文件,使用默认提示词
|
|
52
53
|
try {
|
|
53
54
|
const fs = require('fs');
|
|
@@ -72,7 +73,7 @@ export default function SystemPromptConfigScreen({ onBack }) {
|
|
|
72
73
|
}
|
|
73
74
|
process.exit(0);
|
|
74
75
|
});
|
|
75
|
-
child.on('error',
|
|
76
|
+
child.on('error', error => {
|
|
76
77
|
console.error('Failed to open editor:', error.message);
|
|
77
78
|
process.exit(1);
|
|
78
79
|
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Message } from '../ui/components/MessageList.js';
|
|
2
|
+
/**
|
|
3
|
+
* Format messages to plain text for export
|
|
4
|
+
*/
|
|
5
|
+
export declare function formatMessagesAsText(messages: Message[]): string;
|
|
6
|
+
/**
|
|
7
|
+
* Export messages to a file
|
|
8
|
+
*/
|
|
9
|
+
export declare function exportMessagesToFile(messages: Message[], filePath: string): Promise<void>;
|