snow-ai 0.2.19 → 0.2.21
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/api/systemPrompt.d.ts +1 -1
- package/dist/api/systemPrompt.js +0 -5
- package/dist/cli.js +25 -0
- package/dist/hooks/useCommandHandler.js +24 -16
- package/dist/hooks/useConversation.js +84 -136
- package/dist/hooks/useKeyboardInput.js +9 -0
- package/dist/hooks/useVSCodeState.js +5 -5
- package/dist/mcp/filesystem.d.ts +0 -4
- package/dist/mcp/filesystem.js +68 -54
- package/dist/ui/components/FileList.js +1 -1
- package/dist/ui/components/MarkdownRenderer.js +65 -16
- package/dist/ui/pages/ChatScreen.js +55 -13
- package/dist/utils/commandExecutor.d.ts +1 -0
- package/dist/utils/commands/ide.js +8 -20
- package/dist/utils/vscodeConnection.d.ts +23 -9
- package/dist/utils/vscodeConnection.js +179 -84
- package/package.json +1 -1
package/dist/mcp/filesystem.js
CHANGED
|
@@ -268,13 +268,15 @@ export class FilesystemMCPService {
|
|
|
268
268
|
const end = Math.min(totalLines, endLine);
|
|
269
269
|
// Extract specified lines (convert to 0-indexed) and add line numbers
|
|
270
270
|
const selectedLines = lines.slice(start - 1, end);
|
|
271
|
-
// Format with line numbers (
|
|
272
|
-
//
|
|
273
|
-
const maxLineNumWidth = String(end).length;
|
|
271
|
+
// Format with line numbers (no padding to save tokens)
|
|
272
|
+
// Normalize whitespace: tabs → single space, multiple spaces → single space
|
|
274
273
|
const numberedLines = selectedLines.map((line, index) => {
|
|
275
274
|
const lineNum = start + index;
|
|
276
|
-
|
|
277
|
-
|
|
275
|
+
// Normalize whitespace to reduce token usage
|
|
276
|
+
const normalizedLine = line
|
|
277
|
+
.replace(/\t/g, ' ') // Convert tabs to single space
|
|
278
|
+
.replace(/ +/g, ' '); // Compress multiple spaces to single space
|
|
279
|
+
return `${lineNum}→${normalizedLine}`;
|
|
278
280
|
});
|
|
279
281
|
const partialContent = numberedLines.join('\n');
|
|
280
282
|
return {
|
|
@@ -450,23 +452,31 @@ export class FilesystemMCPService {
|
|
|
450
452
|
// Read the entire file
|
|
451
453
|
const content = await fs.readFile(fullPath, 'utf-8');
|
|
452
454
|
const lines = content.split('\n');
|
|
453
|
-
// Normalize
|
|
455
|
+
// Normalize line endings
|
|
454
456
|
const normalizedSearch = searchContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
455
457
|
const normalizedContent = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
456
|
-
//
|
|
458
|
+
// Apply whitespace normalization for matching (same as getFileContent output)
|
|
459
|
+
const normalizeWhitespace = (text) => text.replace(/\t/g, ' ').replace(/ +/g, ' ');
|
|
460
|
+
const normalizedSearchForMatch = normalizeWhitespace(normalizedSearch);
|
|
461
|
+
const normalizedContentForMatch = normalizeWhitespace(normalizedContent);
|
|
462
|
+
// Find all matches by comparing normalized versions line by line
|
|
463
|
+
// This avoids complex character position mapping
|
|
457
464
|
const matches = [];
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
465
|
+
const searchLines = normalizedSearchForMatch.split('\n');
|
|
466
|
+
const contentLines = normalizedContentForMatch.split('\n');
|
|
467
|
+
for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
|
|
468
|
+
let isMatch = true;
|
|
469
|
+
for (let j = 0; j < searchLines.length; j++) {
|
|
470
|
+
if (contentLines[i + j] !== searchLines[j]) {
|
|
471
|
+
isMatch = false;
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (isMatch) {
|
|
476
|
+
const startLine = i + 1; // Convert to 1-indexed
|
|
477
|
+
const endLine = startLine + searchLines.length - 1;
|
|
478
|
+
matches.push({ startLine, endLine });
|
|
479
|
+
}
|
|
470
480
|
}
|
|
471
481
|
// Handle no matches
|
|
472
482
|
if (matches.length === 0) {
|
|
@@ -485,34 +495,33 @@ export class FilesystemMCPService {
|
|
|
485
495
|
}
|
|
486
496
|
else if (occurrence < 1 || occurrence > matches.length) {
|
|
487
497
|
throw new Error(`Invalid occurrence ${occurrence}. Found ${matches.length} match(es) at lines: ${matches
|
|
488
|
-
.map(m => m.
|
|
498
|
+
.map(m => m.startLine)
|
|
489
499
|
.join(', ')}`);
|
|
490
500
|
}
|
|
491
501
|
else {
|
|
492
502
|
selectedMatch = matches[occurrence - 1];
|
|
493
503
|
}
|
|
494
|
-
const {
|
|
504
|
+
const { startLine, endLine } = selectedMatch;
|
|
495
505
|
// Backup file before editing
|
|
496
506
|
await incrementalSnapshotManager.backupFile(fullPath);
|
|
497
|
-
// Perform the replacement
|
|
507
|
+
// Perform the replacement by replacing the matched lines
|
|
498
508
|
const normalizedReplace = replaceContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
499
|
-
const
|
|
500
|
-
const
|
|
501
|
-
const
|
|
509
|
+
const beforeLines = lines.slice(0, startLine - 1);
|
|
510
|
+
const afterLines = lines.slice(endLine);
|
|
511
|
+
const replaceLines = normalizedReplace.split('\n');
|
|
512
|
+
const modifiedLines = [...beforeLines, ...replaceLines, ...afterLines];
|
|
513
|
+
const modifiedContent = modifiedLines.join('\n');
|
|
502
514
|
// Calculate replaced content for display
|
|
503
515
|
const replacedLines = lines.slice(startLine - 1, endLine);
|
|
504
|
-
const maxLineNumWidth = String(endLine).length;
|
|
505
516
|
const replacedContent = replacedLines
|
|
506
517
|
.map((line, idx) => {
|
|
507
518
|
const lineNum = startLine + idx;
|
|
508
|
-
const
|
|
509
|
-
return `${
|
|
519
|
+
const normalizedLine = line.replace(/\t/g, ' ').replace(/ +/g, ' ');
|
|
520
|
+
return `${lineNum}→${normalizedLine}`;
|
|
510
521
|
})
|
|
511
522
|
.join('\n');
|
|
512
523
|
// Calculate context boundaries
|
|
513
|
-
const
|
|
514
|
-
const replaceLines = normalizedReplace.split('\n');
|
|
515
|
-
const lineDifference = replaceLines.length - (endLine - startLine);
|
|
524
|
+
const lineDifference = replaceLines.length - (endLine - startLine + 1);
|
|
516
525
|
const smartBoundaries = this.findSmartContextBoundaries(lines, startLine, endLine, contextLines);
|
|
517
526
|
const contextStart = smartBoundaries.start;
|
|
518
527
|
const contextEnd = smartBoundaries.end;
|
|
@@ -521,8 +530,8 @@ export class FilesystemMCPService {
|
|
|
521
530
|
const oldContent = oldContextLines
|
|
522
531
|
.map((line, idx) => {
|
|
523
532
|
const lineNum = contextStart + idx;
|
|
524
|
-
const
|
|
525
|
-
return `${
|
|
533
|
+
const normalizedLine = line.replace(/\t/g, ' ').replace(/ +/g, ' ');
|
|
534
|
+
return `${lineNum}→${normalizedLine}`;
|
|
526
535
|
})
|
|
527
536
|
.join('\n');
|
|
528
537
|
// Write the modified content
|
|
@@ -556,8 +565,8 @@ export class FilesystemMCPService {
|
|
|
556
565
|
const newContextContent = newContextLines
|
|
557
566
|
.map((line, idx) => {
|
|
558
567
|
const lineNum = contextStart + idx;
|
|
559
|
-
const
|
|
560
|
-
return `${
|
|
568
|
+
const normalizedLine = line.replace(/\t/g, ' ').replace(/ +/g, ' ');
|
|
569
|
+
return `${lineNum}→${normalizedLine}`;
|
|
561
570
|
})
|
|
562
571
|
.join('\n');
|
|
563
572
|
// Analyze code structure
|
|
@@ -588,19 +597,21 @@ export class FilesystemMCPService {
|
|
|
588
597
|
contextEndLine: finalContextEnd,
|
|
589
598
|
totalLines: finalTotalLines,
|
|
590
599
|
structureAnalysis,
|
|
591
|
-
completeOldContent: content,
|
|
592
|
-
completeNewContent: finalContent,
|
|
593
600
|
diagnostics: undefined,
|
|
594
601
|
};
|
|
595
602
|
// Add diagnostics if found
|
|
596
603
|
if (diagnostics.length > 0) {
|
|
597
|
-
|
|
604
|
+
// Limit diagnostics to top 10 to avoid excessive token usage
|
|
605
|
+
const limitedDiagnostics = diagnostics.slice(0, 10);
|
|
606
|
+
result.diagnostics = limitedDiagnostics;
|
|
598
607
|
const errorCount = diagnostics.filter(d => d.severity === 'error').length;
|
|
599
608
|
const warningCount = diagnostics.filter(d => d.severity === 'warning').length;
|
|
600
609
|
if (errorCount > 0 || warningCount > 0) {
|
|
601
610
|
result.message += `\n\n⚠️ Diagnostics detected: ${errorCount} error(s), ${warningCount} warning(s)`;
|
|
611
|
+
// Format diagnostics for better readability (limit to first 5 for message display)
|
|
602
612
|
const formattedDiagnostics = diagnostics
|
|
603
613
|
.filter(d => d.severity === 'error' || d.severity === 'warning')
|
|
614
|
+
.slice(0, 5)
|
|
604
615
|
.map(d => {
|
|
605
616
|
const icon = d.severity === 'error' ? '❌' : '⚠️';
|
|
606
617
|
const location = `${filePath}:${d.line}:${d.character}`;
|
|
@@ -608,6 +619,9 @@ export class FilesystemMCPService {
|
|
|
608
619
|
})
|
|
609
620
|
.join('\n\n');
|
|
610
621
|
result.message += `\n\n📋 Diagnostic Details:\n${formattedDiagnostics}`;
|
|
622
|
+
if (errorCount + warningCount > 5) {
|
|
623
|
+
result.message += `\n ... and ${errorCount + warningCount - 5} more issue(s)`;
|
|
624
|
+
}
|
|
611
625
|
result.message += `\n\n ⚡ TIP: Review the errors above and make another edit to fix them`;
|
|
612
626
|
}
|
|
613
627
|
}
|
|
@@ -700,8 +714,8 @@ export class FilesystemMCPService {
|
|
|
700
714
|
const replacedContent = replacedLines
|
|
701
715
|
.map((line, idx) => {
|
|
702
716
|
const lineNum = startLine + idx;
|
|
703
|
-
const
|
|
704
|
-
return `${
|
|
717
|
+
const normalizedLine = line.replace(/\t/g, ' ').replace(/ +/g, ' ');
|
|
718
|
+
return `${lineNum}→${normalizedLine}`;
|
|
705
719
|
})
|
|
706
720
|
.join('\n');
|
|
707
721
|
// Calculate context range using smart boundary detection
|
|
@@ -713,8 +727,8 @@ export class FilesystemMCPService {
|
|
|
713
727
|
const oldContent = oldContextLines
|
|
714
728
|
.map((line, idx) => {
|
|
715
729
|
const lineNum = contextStart + idx;
|
|
716
|
-
const
|
|
717
|
-
return `${
|
|
730
|
+
const normalizedLine = line.replace(/\t/g, ' ').replace(/ +/g, ' ');
|
|
731
|
+
return `${lineNum}→${normalizedLine}`;
|
|
718
732
|
})
|
|
719
733
|
.join('\n');
|
|
720
734
|
// Replace the specified lines
|
|
@@ -731,8 +745,8 @@ export class FilesystemMCPService {
|
|
|
731
745
|
const newContextContent = newContextLines
|
|
732
746
|
.map((line, idx) => {
|
|
733
747
|
const lineNum = contextStart + idx;
|
|
734
|
-
const
|
|
735
|
-
return `${
|
|
748
|
+
const normalizedLine = line.replace(/\t/g, ' ').replace(/ +/g, ' ');
|
|
749
|
+
return `${lineNum}→${normalizedLine}`;
|
|
736
750
|
})
|
|
737
751
|
.join('\n');
|
|
738
752
|
// Write the modified content back to file
|
|
@@ -762,8 +776,8 @@ export class FilesystemMCPService {
|
|
|
762
776
|
finalContextContent = formattedContextLines
|
|
763
777
|
.map((line, idx) => {
|
|
764
778
|
const lineNum = contextStart + idx;
|
|
765
|
-
const
|
|
766
|
-
return `${
|
|
779
|
+
const normalizedLine = line.replace(/\t/g, ' ').replace(/ +/g, ' ');
|
|
780
|
+
return `${lineNum}→${normalizedLine}`;
|
|
767
781
|
})
|
|
768
782
|
.join('\n');
|
|
769
783
|
}
|
|
@@ -785,9 +799,6 @@ export class FilesystemMCPService {
|
|
|
785
799
|
catch (error) {
|
|
786
800
|
// Ignore diagnostics errors, they are optional
|
|
787
801
|
}
|
|
788
|
-
// Prepare complete file contents (without line numbers for diff comparison)
|
|
789
|
-
const completeOldContent = lines.join('\n');
|
|
790
|
-
const completeNewContent = finalLines.join('\n');
|
|
791
802
|
const result = {
|
|
792
803
|
message: `✅ File edited successfully,Please check the edit results and pay attention to code boundary issues to avoid syntax errors caused by missing closed parts: ${filePath}\n` +
|
|
793
804
|
` Replaced: lines ${startLine}-${adjustedEndLine} (${linesToModify} lines)\n` +
|
|
@@ -803,20 +814,20 @@ export class FilesystemMCPService {
|
|
|
803
814
|
totalLines: finalTotalLines,
|
|
804
815
|
linesModified: linesToModify,
|
|
805
816
|
structureAnalysis,
|
|
806
|
-
// Add complete file contents for intelligent diff display
|
|
807
|
-
completeOldContent,
|
|
808
|
-
completeNewContent,
|
|
809
817
|
};
|
|
810
818
|
// Add diagnostics if any were found
|
|
811
819
|
if (diagnostics.length > 0) {
|
|
812
|
-
|
|
820
|
+
// Limit diagnostics to top 10 to avoid excessive token usage
|
|
821
|
+
const limitedDiagnostics = diagnostics.slice(0, 10);
|
|
822
|
+
result.diagnostics = limitedDiagnostics;
|
|
813
823
|
const errorCount = diagnostics.filter(d => d.severity === 'error').length;
|
|
814
824
|
const warningCount = diagnostics.filter(d => d.severity === 'warning').length;
|
|
815
825
|
if (errorCount > 0 || warningCount > 0) {
|
|
816
826
|
result.message += `\n\n⚠️ Diagnostics detected: ${errorCount} error(s), ${warningCount} warning(s)`;
|
|
817
|
-
// Format diagnostics for better readability
|
|
827
|
+
// Format diagnostics for better readability (limit to first 5 for message display)
|
|
818
828
|
const formattedDiagnostics = diagnostics
|
|
819
829
|
.filter(d => d.severity === 'error' || d.severity === 'warning')
|
|
830
|
+
.slice(0, 5)
|
|
820
831
|
.map(d => {
|
|
821
832
|
const icon = d.severity === 'error' ? '❌' : '⚠️';
|
|
822
833
|
const location = `${filePath}:${d.line}:${d.character}`;
|
|
@@ -824,6 +835,9 @@ export class FilesystemMCPService {
|
|
|
824
835
|
})
|
|
825
836
|
.join('\n\n');
|
|
826
837
|
result.message += `\n\n📋 Diagnostic Details:\n${formattedDiagnostics}`;
|
|
838
|
+
if (errorCount + warningCount > 5) {
|
|
839
|
+
result.message += `\n ... and ${errorCount + warningCount - 5} more issue(s)`;
|
|
840
|
+
}
|
|
827
841
|
result.message += `\n\n ⚡ TIP: Review the errors above and make another small edit to fix them`;
|
|
828
842
|
}
|
|
829
843
|
}
|
|
@@ -177,7 +177,7 @@ const FileList = memo(forwardRef(({ query, selectedIndex, visible, maxItems = 10
|
|
|
177
177
|
return (React.createElement(Box, { paddingX: 1, marginTop: 1, flexDirection: "column" },
|
|
178
178
|
React.createElement(Box, { marginBottom: 1 },
|
|
179
179
|
React.createElement(Text, { color: "blue", bold: true },
|
|
180
|
-
"\
|
|
180
|
+
"\u2261 Files",
|
|
181
181
|
' ',
|
|
182
182
|
allFilteredFiles.length > effectiveMaxItems &&
|
|
183
183
|
`(${selectedIndex + 1}/${allFilteredFiles.length})`)),
|
|
@@ -11,8 +11,23 @@ export default function MarkdownRenderer({ content, color }) {
|
|
|
11
11
|
block.language && (React.createElement(Text, { backgroundColor: "green", color: "white" }, block.language)),
|
|
12
12
|
React.createElement(Text, null, highlightCode(block.code, block.language))))));
|
|
13
13
|
}
|
|
14
|
+
// Render heading
|
|
15
|
+
if (block.type === 'heading') {
|
|
16
|
+
const headingColors = ['cyan', 'blue', 'magenta', 'yellow'];
|
|
17
|
+
const headingColor = headingColors[block.level - 1] || 'white';
|
|
18
|
+
return (React.createElement(Box, { key: index, marginY: 0 },
|
|
19
|
+
React.createElement(Text, { bold: true, color: headingColor }, renderInlineFormatting(block.content))));
|
|
20
|
+
}
|
|
21
|
+
// Render list
|
|
22
|
+
if (block.type === 'list') {
|
|
23
|
+
return (React.createElement(Box, { key: index, flexDirection: "column", marginY: 0 }, block.items.map((item, itemIndex) => (React.createElement(Box, { key: itemIndex },
|
|
24
|
+
React.createElement(Text, { color: "yellow" }, "\u2022 "),
|
|
25
|
+
React.createElement(Text, { color: color }, renderInlineFormatting(item)))))));
|
|
26
|
+
}
|
|
14
27
|
// Render text with inline formatting
|
|
15
|
-
return (React.createElement(Box, { key: index, flexDirection: "column" }, block.content
|
|
28
|
+
return (React.createElement(Box, { key: index, flexDirection: "column" }, block.content
|
|
29
|
+
.split('\n')
|
|
30
|
+
.map((line, lineIndex) => (React.createElement(Text, { key: lineIndex, color: color }, line === '' ? ' ' : renderInlineFormatting(line))))));
|
|
16
31
|
})));
|
|
17
32
|
}
|
|
18
33
|
function parseMarkdown(content) {
|
|
@@ -44,11 +59,45 @@ function parseMarkdown(content) {
|
|
|
44
59
|
i++; // Skip closing ```
|
|
45
60
|
continue;
|
|
46
61
|
}
|
|
47
|
-
//
|
|
62
|
+
// Check for heading (# ## ### ####)
|
|
63
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
64
|
+
if (headingMatch) {
|
|
65
|
+
blocks.push({
|
|
66
|
+
type: 'heading',
|
|
67
|
+
level: headingMatch[1].length,
|
|
68
|
+
content: headingMatch[2].trim(),
|
|
69
|
+
});
|
|
70
|
+
i++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// Check for list item (* or -)
|
|
74
|
+
const listMatch = line.match(/^[\s]*[*\-]\s+(.+)$/);
|
|
75
|
+
if (listMatch) {
|
|
76
|
+
const listItems = [listMatch[1].trim()];
|
|
77
|
+
i++;
|
|
78
|
+
// Collect consecutive list items
|
|
79
|
+
while (i < lines.length) {
|
|
80
|
+
const currentLine = lines[i] ?? '';
|
|
81
|
+
const nextListMatch = currentLine.match(/^[\s]*[*\-]\s+(.+)$/);
|
|
82
|
+
if (!nextListMatch) {
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
listItems.push(nextListMatch[1].trim());
|
|
86
|
+
i++;
|
|
87
|
+
}
|
|
88
|
+
blocks.push({
|
|
89
|
+
type: 'list',
|
|
90
|
+
items: listItems,
|
|
91
|
+
});
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
// Collect text lines until next code block, heading, or list
|
|
48
95
|
const textLines = [];
|
|
49
96
|
while (i < lines.length) {
|
|
50
97
|
const currentLine = lines[i] ?? '';
|
|
51
|
-
if (currentLine.trim().startsWith('```')
|
|
98
|
+
if (currentLine.trim().startsWith('```') ||
|
|
99
|
+
currentLine.match(/^#{1,6}\s+/) ||
|
|
100
|
+
currentLine.match(/^[\s]*[*\-]\s+/)) {
|
|
52
101
|
break;
|
|
53
102
|
}
|
|
54
103
|
textLines.push(currentLine);
|
|
@@ -71,18 +120,18 @@ function highlightCode(code, language) {
|
|
|
71
120
|
}
|
|
72
121
|
// Map common language aliases to cli-highlight supported names
|
|
73
122
|
const languageMap = {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
123
|
+
js: 'javascript',
|
|
124
|
+
ts: 'typescript',
|
|
125
|
+
py: 'python',
|
|
126
|
+
rb: 'ruby',
|
|
127
|
+
sh: 'bash',
|
|
128
|
+
shell: 'bash',
|
|
129
|
+
cs: 'csharp',
|
|
81
130
|
'c#': 'csharp',
|
|
82
|
-
|
|
131
|
+
cpp: 'cpp',
|
|
83
132
|
'c++': 'cpp',
|
|
84
|
-
|
|
85
|
-
|
|
133
|
+
yml: 'yaml',
|
|
134
|
+
md: 'markdown',
|
|
86
135
|
};
|
|
87
136
|
const mappedLanguage = languageMap[language.toLowerCase()] || language.toLowerCase();
|
|
88
137
|
return highlight(code, { language: mappedLanguage, ignoreIllegals: true });
|
|
@@ -93,16 +142,16 @@ function highlightCode(code, language) {
|
|
|
93
142
|
}
|
|
94
143
|
}
|
|
95
144
|
function renderInlineFormatting(text) {
|
|
96
|
-
// Handle inline code `code`
|
|
145
|
+
// Handle inline code `code` - remove backticks
|
|
97
146
|
text = text.replace(/`([^`]+)`/g, (_, code) => {
|
|
98
147
|
// Use ANSI codes for inline code styling
|
|
99
148
|
return `\x1b[36m${code}\x1b[0m`;
|
|
100
149
|
});
|
|
101
|
-
// Handle bold **text** or __text__
|
|
150
|
+
// Handle bold **text** or __text__ - remove markers
|
|
102
151
|
text = text.replace(/(\*\*|__)([^*_]+)\1/g, (_, __, content) => {
|
|
103
152
|
return `\x1b[1m${content}\x1b[0m`;
|
|
104
153
|
});
|
|
105
|
-
// Handle italic *text* or _text_ (but not part of bold)
|
|
154
|
+
// Handle italic *text* or _text_ (but not part of bold) - remove markers
|
|
106
155
|
text = text.replace(/(?<!\*)(\*)(?!\*)([^*]+)\1(?!\*)/g, (_, __, content) => {
|
|
107
156
|
return `\x1b[3m${content}\x1b[0m`;
|
|
108
157
|
});
|
|
@@ -24,6 +24,7 @@ import { useSnapshotState } from '../../hooks/useSnapshotState.js';
|
|
|
24
24
|
import { useStreamingState } from '../../hooks/useStreamingState.js';
|
|
25
25
|
import { useCommandHandler } from '../../hooks/useCommandHandler.js';
|
|
26
26
|
import { parseAndValidateFileReferences, createMessageWithFileInstructions, getSystemInfo, } from '../../utils/fileUtils.js';
|
|
27
|
+
import { executeCommand } from '../../utils/commandExecutor.js';
|
|
27
28
|
import { convertSessionMessagesToUI } from '../../utils/sessionConverter.js';
|
|
28
29
|
import { incrementalSnapshotManager } from '../../utils/incrementalSnapshot.js';
|
|
29
30
|
import { formatElapsedTime } from '../../utils/textUtils.js';
|
|
@@ -41,6 +42,7 @@ export default function ChatScreen({}) {
|
|
|
41
42
|
const [currentTodos, setCurrentTodos] = useState([]);
|
|
42
43
|
const [pendingMessages, setPendingMessages] = useState([]);
|
|
43
44
|
const pendingMessagesRef = useRef([]);
|
|
45
|
+
const hasAttemptedAutoVscodeConnect = useRef(false);
|
|
44
46
|
const [remountKey, setRemountKey] = useState(0);
|
|
45
47
|
const [showMcpInfo, setShowMcpInfo] = useState(false);
|
|
46
48
|
const [mcpPanelKey, setMcpPanelKey] = useState(0);
|
|
@@ -105,6 +107,31 @@ export default function ChatScreen({}) {
|
|
|
105
107
|
setVscodeConnectionStatus: vscodeState.setVscodeConnectionStatus,
|
|
106
108
|
processMessage: (message, images, useBasicModel, hideUserMessage) => processMessageRef.current?.(message, images, useBasicModel, hideUserMessage) || Promise.resolve(),
|
|
107
109
|
});
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (hasAttemptedAutoVscodeConnect.current) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (vscodeState.vscodeConnectionStatus !== 'disconnected') {
|
|
115
|
+
hasAttemptedAutoVscodeConnect.current = true;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
hasAttemptedAutoVscodeConnect.current = true;
|
|
119
|
+
(async () => {
|
|
120
|
+
try {
|
|
121
|
+
const result = await executeCommand('ide');
|
|
122
|
+
await handleCommandExecution('ide', result);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.error('Failed to auto-connect VSCode:', error);
|
|
126
|
+
await handleCommandExecution('ide', {
|
|
127
|
+
success: false,
|
|
128
|
+
message: error instanceof Error
|
|
129
|
+
? error.message
|
|
130
|
+
: 'Failed to start VSCode connection',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
})();
|
|
134
|
+
}, [handleCommandExecution, vscodeState.vscodeConnectionStatus]);
|
|
108
135
|
// Pending messages are now handled inline during tool execution in useConversation
|
|
109
136
|
// Auto-send pending messages when streaming completely stops (as fallback)
|
|
110
137
|
useEffect(() => {
|
|
@@ -142,7 +169,9 @@ export default function ChatScreen({}) {
|
|
|
142
169
|
}
|
|
143
170
|
return;
|
|
144
171
|
}
|
|
145
|
-
if (key.escape &&
|
|
172
|
+
if (key.escape &&
|
|
173
|
+
streamingState.isStreaming &&
|
|
174
|
+
streamingState.abortController) {
|
|
146
175
|
// Abort the controller
|
|
147
176
|
streamingState.abortController.abort();
|
|
148
177
|
// Add discontinued message
|
|
@@ -427,6 +456,7 @@ export default function ChatScreen({}) {
|
|
|
427
456
|
React.createElement(Text, { color: "white" }, " \u26C7")),
|
|
428
457
|
React.createElement(Text, null, "\u2022 Ask for code explanations and debugging help"),
|
|
429
458
|
React.createElement(Text, null, "\u2022 Press ESC during response to interrupt"),
|
|
459
|
+
React.createElement(Text, null, "\u2022 Press Shift+Tab: toggle YOLO"),
|
|
430
460
|
React.createElement(Text, null,
|
|
431
461
|
"\u2022 Working directory: ",
|
|
432
462
|
workingDirectory)))),
|
|
@@ -493,12 +523,17 @@ export default function ChatScreen({}) {
|
|
|
493
523
|
message.toolCall.name === 'filesystem-edit' &&
|
|
494
524
|
message.toolCall.arguments.oldContent &&
|
|
495
525
|
message.toolCall.arguments.newContent && (React.createElement(Box, { marginTop: 1 },
|
|
496
|
-
React.createElement(DiffViewer, { oldContent: message.toolCall.arguments.oldContent, newContent: message.toolCall.arguments.newContent, filename: message.toolCall.arguments.filename, completeOldContent: message.toolCall.arguments
|
|
526
|
+
React.createElement(DiffViewer, { oldContent: message.toolCall.arguments.oldContent, newContent: message.toolCall.arguments.newContent, filename: message.toolCall.arguments.filename, completeOldContent: message.toolCall.arguments
|
|
527
|
+
.completeOldContent, completeNewContent: message.toolCall.arguments
|
|
528
|
+
.completeNewContent, startLineNumber: message.toolCall.arguments.contextStartLine }))),
|
|
497
529
|
message.toolCall &&
|
|
498
|
-
message.toolCall.name ===
|
|
530
|
+
message.toolCall.name ===
|
|
531
|
+
'filesystem-edit_search' &&
|
|
499
532
|
message.toolCall.arguments.oldContent &&
|
|
500
533
|
message.toolCall.arguments.newContent && (React.createElement(Box, { marginTop: 1 },
|
|
501
|
-
React.createElement(DiffViewer, { oldContent: message.toolCall.arguments.oldContent, newContent: message.toolCall.arguments.newContent, filename: message.toolCall.arguments.filename, completeOldContent: message.toolCall.arguments
|
|
534
|
+
React.createElement(DiffViewer, { oldContent: message.toolCall.arguments.oldContent, newContent: message.toolCall.arguments.newContent, filename: message.toolCall.arguments.filename, completeOldContent: message.toolCall.arguments
|
|
535
|
+
.completeOldContent, completeNewContent: message.toolCall.arguments
|
|
536
|
+
.completeNewContent, startLineNumber: message.toolCall.arguments.contextStartLine }))),
|
|
502
537
|
message.toolCall &&
|
|
503
538
|
message.toolCall.name === 'terminal-execute' &&
|
|
504
539
|
message.toolCall.arguments.command && (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
@@ -580,24 +615,31 @@ export default function ChatScreen({}) {
|
|
|
580
615
|
React.createElement(Spinner, { type: "dots" }))))))),
|
|
581
616
|
(streamingState.isStreaming || isSaving) && !pendingToolConfirmation && (React.createElement(Box, { marginBottom: 1, paddingX: 1, width: "100%" },
|
|
582
617
|
React.createElement(Text, { color: ['#FF6EBF', 'green', 'blue', 'cyan', '#B588F8'][streamingState.animationFrame], bold: true }, "\u2746"),
|
|
583
|
-
React.createElement(Box, { marginLeft: 1, marginBottom: 1, flexDirection: "column" }, streamingState.isStreaming ? (React.createElement(React.Fragment, null, streamingState.retryStatus &&
|
|
618
|
+
React.createElement(Box, { marginLeft: 1, marginBottom: 1, flexDirection: "column" }, streamingState.isStreaming ? (React.createElement(React.Fragment, null, streamingState.retryStatus &&
|
|
619
|
+
streamingState.retryStatus.isRetrying ? (
|
|
584
620
|
// Retry status display - hide "Thinking" and show retry info
|
|
585
621
|
React.createElement(Box, { flexDirection: "column" },
|
|
586
622
|
streamingState.retryStatus.errorMessage && (React.createElement(Text, { color: "red", dimColor: true },
|
|
587
623
|
"\u2717 Error: ",
|
|
588
624
|
streamingState.retryStatus.errorMessage)),
|
|
589
|
-
streamingState.retryStatus.remainingSeconds !==
|
|
625
|
+
streamingState.retryStatus.remainingSeconds !==
|
|
626
|
+
undefined &&
|
|
627
|
+
streamingState.retryStatus.remainingSeconds > 0 ? (React.createElement(Text, { color: "yellow", dimColor: true },
|
|
590
628
|
"\u27F3 Retry ",
|
|
591
629
|
streamingState.retryStatus.attempt,
|
|
592
|
-
"/5 in
|
|
630
|
+
"/5 in",
|
|
631
|
+
' ',
|
|
593
632
|
streamingState.retryStatus.remainingSeconds,
|
|
594
633
|
"s...")) : (React.createElement(Text, { color: "yellow", dimColor: true },
|
|
595
|
-
"\u27F3 Resending... (Attempt
|
|
634
|
+
"\u27F3 Resending... (Attempt",
|
|
635
|
+
' ',
|
|
596
636
|
streamingState.retryStatus.attempt,
|
|
597
637
|
"/5)")))) : (
|
|
598
638
|
// Normal thinking status
|
|
599
639
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
600
|
-
React.createElement(ShimmerText, { text: streamingState.isReasoning
|
|
640
|
+
React.createElement(ShimmerText, { text: streamingState.isReasoning
|
|
641
|
+
? 'Deep thinking...'
|
|
642
|
+
: 'Thinking...' }),
|
|
601
643
|
' ',
|
|
602
644
|
"(",
|
|
603
645
|
formatElapsedTime(streamingState.elapsedSeconds),
|
|
@@ -649,12 +691,12 @@ export default function ChatScreen({}) {
|
|
|
649
691
|
"\u25CF",
|
|
650
692
|
' ',
|
|
651
693
|
vscodeState.vscodeConnectionStatus === 'connecting'
|
|
652
|
-
? '
|
|
694
|
+
? 'Connecting to IDE...'
|
|
653
695
|
: vscodeState.vscodeConnectionStatus === 'connected'
|
|
654
|
-
? '
|
|
696
|
+
? 'IDE Connected'
|
|
655
697
|
: vscodeState.vscodeConnectionStatus === 'error'
|
|
656
|
-
? 'Connection Failed - Make sure Snow CLI
|
|
657
|
-
: '
|
|
698
|
+
? 'Connection Failed - Make sure Snow CLI plugin is installed and active in your IDE'
|
|
699
|
+
: 'IDE',
|
|
658
700
|
vscodeState.vscodeConnectionStatus === 'connected' &&
|
|
659
701
|
vscodeState.editorContext.activeFile &&
|
|
660
702
|
` | ${vscodeState.editorContext.activeFile}`,
|
|
@@ -3,6 +3,7 @@ export interface CommandResult {
|
|
|
3
3
|
message?: string;
|
|
4
4
|
action?: 'clear' | 'resume' | 'info' | 'showMcpInfo' | 'goHome' | 'toggleYolo' | 'initProject' | 'compact' | 'showSessionPanel' | 'showMcpPanel';
|
|
5
5
|
prompt?: string;
|
|
6
|
+
alreadyConnected?: boolean;
|
|
6
7
|
}
|
|
7
8
|
export interface CommandHandler {
|
|
8
9
|
execute: () => Promise<CommandResult> | CommandResult;
|
|
@@ -3,42 +3,30 @@ import { vscodeConnection } from '../vscodeConnection.js';
|
|
|
3
3
|
// IDE connection command handler
|
|
4
4
|
registerCommand('ide', {
|
|
5
5
|
execute: async () => {
|
|
6
|
-
// Check if already connected
|
|
6
|
+
// Check if already connected to IDE plugin
|
|
7
7
|
if (vscodeConnection.isConnected()) {
|
|
8
8
|
return {
|
|
9
9
|
success: true,
|
|
10
10
|
action: 'info',
|
|
11
|
-
|
|
11
|
+
alreadyConnected: true,
|
|
12
|
+
message: `Already connected to IDE (port ${vscodeConnection.getPort()})`
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
|
-
//
|
|
15
|
-
if (vscodeConnection.isServerRunning()) {
|
|
16
|
-
return {
|
|
17
|
-
success: true,
|
|
18
|
-
action: 'info',
|
|
19
|
-
message: `VSCode connection server is already running on port ${vscodeConnection.getPort()}\nWaiting for VSCode extension to connect...`
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
// Start the server
|
|
15
|
+
// Try to connect to IDE plugin server
|
|
23
16
|
try {
|
|
24
17
|
await vscodeConnection.start();
|
|
25
18
|
return {
|
|
26
19
|
success: true,
|
|
27
20
|
action: 'info',
|
|
28
|
-
message: `
|
|
21
|
+
message: `Connected to IDE on port ${vscodeConnection.getPort()}\nMake sure your IDE plugin (VSCode/JetBrains) is active and running.`
|
|
29
22
|
};
|
|
30
23
|
}
|
|
31
24
|
catch (error) {
|
|
32
|
-
// Handle EADDRINUSE error specifically
|
|
33
|
-
if (error instanceof Error && 'code' in error && error.code === 'EADDRINUSE') {
|
|
34
|
-
return {
|
|
35
|
-
success: false,
|
|
36
|
-
message: `Port ${vscodeConnection.getPort()} is already in use. Please restart Snow CLI to reset the connection.`
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
25
|
return {
|
|
40
26
|
success: false,
|
|
41
|
-
message: error instanceof Error
|
|
27
|
+
message: error instanceof Error
|
|
28
|
+
? `Failed to connect to IDE: ${error.message}\nMake sure your IDE plugin is installed and active.`
|
|
29
|
+
: 'Failed to connect to IDE. Make sure your IDE plugin is installed and active.'
|
|
42
30
|
};
|
|
43
31
|
}
|
|
44
32
|
}
|