snow-ai 0.4.8 → 0.4.10
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/app.js +30 -12
- package/dist/cli.js +6 -0
- package/dist/i18n/lang/en.js +21 -0
- package/dist/i18n/lang/es.js +21 -0
- package/dist/i18n/lang/ja.js +21 -0
- package/dist/i18n/lang/ko.js +21 -0
- package/dist/i18n/lang/zh-TW.js +21 -0
- package/dist/i18n/lang/zh.js +21 -0
- package/dist/i18n/types.d.ts +21 -0
- package/dist/mcp/todo.js +1 -1
- package/dist/ui/components/AgentPickerPanel.js +8 -6
- package/dist/ui/components/ChatInput.js +23 -21
- package/dist/ui/components/CommandPanel.js +7 -5
- package/dist/ui/components/DiffViewer.js +6 -4
- package/dist/ui/components/FileList.js +8 -6
- package/dist/ui/components/Menu.d.ts +1 -1
- package/dist/ui/components/Menu.js +8 -6
- package/dist/ui/components/PendingMessages.js +7 -5
- package/dist/ui/components/TodoPickerPanel.js +12 -10
- package/dist/ui/components/TodoTree.js +7 -5
- package/dist/ui/components/ToolConfirmation.js +14 -12
- package/dist/ui/components/ToolResultPreview.js +17 -3
- package/dist/ui/contexts/ThemeContext.d.ts +13 -0
- package/dist/ui/contexts/ThemeContext.js +28 -0
- package/dist/ui/pages/ChatScreen.js +21 -19
- package/dist/ui/pages/CodeBaseConfigScreen.js +30 -28
- package/dist/ui/pages/ConfigScreen.js +76 -74
- package/dist/ui/pages/CustomHeadersScreen.js +33 -31
- package/dist/ui/pages/LanguageSettingsScreen.js +6 -4
- package/dist/ui/pages/ProxyConfigScreen.js +15 -13
- package/dist/ui/pages/SensitiveCommandConfigScreen.js +12 -10
- package/dist/ui/pages/SubAgentConfigScreen.js +12 -10
- package/dist/ui/pages/SubAgentListScreen.js +11 -9
- package/dist/ui/pages/SystemPromptConfigScreen.js +21 -19
- package/dist/ui/pages/ThemeSettingsScreen.d.ts +7 -0
- package/dist/ui/pages/ThemeSettingsScreen.js +106 -0
- package/dist/ui/pages/WelcomeScreen.js +11 -1
- package/dist/ui/themes/index.d.ts +23 -0
- package/dist/ui/themes/index.js +140 -0
- package/dist/utils/configManager.js +11 -3
- package/dist/utils/logger.d.ts +9 -3
- package/dist/utils/logger.js +28 -3
- package/dist/utils/mcpToolsManager.d.ts +1 -1
- package/dist/utils/mcpToolsManager.js +13 -9
- package/dist/utils/themeConfig.d.ts +21 -0
- package/dist/utils/themeConfig.js +61 -0
- package/dist/utils/toolExecutor.js +11 -1
- package/package.json +3 -2
|
@@ -3,7 +3,9 @@ import { Box, Text } from 'ink';
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
|
|
6
|
+
import { useTheme } from '../contexts/ThemeContext.js';
|
|
6
7
|
const FileList = memo(forwardRef(({ query, selectedIndex, visible, maxItems = 10, rootPath = process.cwd(), onFilteredCountChange, searchMode = 'file', }, ref) => {
|
|
8
|
+
const { theme } = useTheme();
|
|
7
9
|
const [files, setFiles] = useState([]);
|
|
8
10
|
const [isLoading, setIsLoading] = useState(false);
|
|
9
11
|
// Get terminal size for dynamic content display
|
|
@@ -325,7 +327,7 @@ const FileList = memo(forwardRef(({ query, selectedIndex, visible, maxItems = 10
|
|
|
325
327
|
}
|
|
326
328
|
if (filteredFiles.length === 0) {
|
|
327
329
|
return (React.createElement(Box, { paddingX: 1, marginTop: 1 },
|
|
328
|
-
React.createElement(Text, { color:
|
|
330
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, "No files found")));
|
|
329
331
|
}
|
|
330
332
|
return (React.createElement(Box, { paddingX: 1, marginTop: 1, flexDirection: "column" },
|
|
331
333
|
React.createElement(Box, { marginBottom: 1 },
|
|
@@ -335,20 +337,20 @@ const FileList = memo(forwardRef(({ query, selectedIndex, visible, maxItems = 10
|
|
|
335
337
|
allFilteredFiles.length > effectiveMaxItems &&
|
|
336
338
|
`(${selectedIndex + 1}/${allFilteredFiles.length})`)),
|
|
337
339
|
filteredFiles.map((file, index) => (React.createElement(Box, { key: `${file.path}-${file.lineNumber || 0}`, flexDirection: "column" },
|
|
338
|
-
React.createElement(Text, { backgroundColor: index === displaySelectedIndex ?
|
|
339
|
-
?
|
|
340
|
+
React.createElement(Text, { backgroundColor: index === displaySelectedIndex ? theme.colors.menuSelected : undefined, color: index === displaySelectedIndex
|
|
341
|
+
? theme.colors.menuNormal
|
|
340
342
|
: file.isDirectory
|
|
341
|
-
?
|
|
343
|
+
? theme.colors.warning
|
|
342
344
|
: 'white' }, searchMode === 'content' && file.lineNumber !== undefined
|
|
343
345
|
? `${file.path}:${file.lineNumber}`
|
|
344
346
|
: file.isDirectory
|
|
345
347
|
? '◇ ' + file.path
|
|
346
348
|
: '◆ ' + file.path),
|
|
347
|
-
searchMode === 'content' && file.lineContent && (React.createElement(Text, { backgroundColor: index === displaySelectedIndex ?
|
|
349
|
+
searchMode === 'content' && file.lineContent && (React.createElement(Text, { backgroundColor: index === displaySelectedIndex ? theme.colors.menuSelected : undefined, color: index === displaySelectedIndex ? theme.colors.menuSecondary : theme.colors.menuSecondary, dimColor: true },
|
|
348
350
|
' ',
|
|
349
351
|
file.lineContent))))),
|
|
350
352
|
allFilteredFiles.length > effectiveMaxItems && (React.createElement(Box, { marginTop: 1 },
|
|
351
|
-
React.createElement(Text, { color:
|
|
353
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true },
|
|
352
354
|
"\u2191\u2193 to scroll \u00B7 ",
|
|
353
355
|
allFilteredFiles.length - effectiveMaxItems,
|
|
354
356
|
' ',
|
|
@@ -9,7 +9,7 @@ type MenuOption = {
|
|
|
9
9
|
type Props = {
|
|
10
10
|
options: MenuOption[];
|
|
11
11
|
onSelect: (value: string) => void;
|
|
12
|
-
onSelectionChange?: (infoText: string) => void;
|
|
12
|
+
onSelectionChange?: (infoText: string, value: string) => void;
|
|
13
13
|
maxHeight?: number;
|
|
14
14
|
};
|
|
15
15
|
declare function Menu({ options, onSelect, onSelectionChange, maxHeight }: Props): React.JSX.Element;
|
|
@@ -2,11 +2,13 @@ import React, { useState, useCallback } from 'react';
|
|
|
2
2
|
import { Box, Text, useInput, useStdout } from 'ink';
|
|
3
3
|
import { resetTerminal } from '../../utils/terminal.js';
|
|
4
4
|
import { useI18n } from '../../i18n/index.js';
|
|
5
|
+
import { useTheme } from '../contexts/ThemeContext.js';
|
|
5
6
|
function Menu({ options, onSelect, onSelectionChange, maxHeight }) {
|
|
6
7
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
7
8
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
8
9
|
const { stdout } = useStdout();
|
|
9
10
|
const { t } = useI18n();
|
|
11
|
+
const { theme } = useTheme();
|
|
10
12
|
// Calculate available height
|
|
11
13
|
const terminalHeight = stdout?.rows || 24;
|
|
12
14
|
const headerHeight = 8; // Space for header, borders, etc.
|
|
@@ -15,7 +17,7 @@ function Menu({ options, onSelect, onSelectionChange, maxHeight }) {
|
|
|
15
17
|
React.useEffect(() => {
|
|
16
18
|
const currentOption = options[selectedIndex];
|
|
17
19
|
if (onSelectionChange && currentOption?.infoText) {
|
|
18
|
-
onSelectionChange(currentOption.infoText);
|
|
20
|
+
onSelectionChange(currentOption.infoText, currentOption.value);
|
|
19
21
|
}
|
|
20
22
|
}, [selectedIndex, options, onSelectionChange]);
|
|
21
23
|
// Auto-scroll to keep selected item visible
|
|
@@ -56,9 +58,9 @@ function Menu({ options, onSelect, onSelectionChange, maxHeight }) {
|
|
|
56
58
|
const moreBelowCount = options.length - (scrollOffset + visibleItemCount);
|
|
57
59
|
return (React.createElement(Box, { flexDirection: "column", width: '100%', padding: 1 },
|
|
58
60
|
React.createElement(Box, { marginBottom: 1 },
|
|
59
|
-
React.createElement(Text, { color:
|
|
61
|
+
React.createElement(Text, { color: theme.colors.menuInfo }, t.menu.navigate)),
|
|
60
62
|
hasMoreAbove && (React.createElement(Box, null,
|
|
61
|
-
React.createElement(Text, { color:
|
|
63
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true },
|
|
62
64
|
"\u2191 +",
|
|
63
65
|
moreAboveCount,
|
|
64
66
|
" more above"))),
|
|
@@ -66,13 +68,13 @@ function Menu({ options, onSelect, onSelectionChange, maxHeight }) {
|
|
|
66
68
|
const actualIndex = scrollOffset + index;
|
|
67
69
|
return (React.createElement(Box, { key: option.value },
|
|
68
70
|
React.createElement(Text, { color: actualIndex === selectedIndex
|
|
69
|
-
?
|
|
70
|
-
: option.color ||
|
|
71
|
+
? theme.colors.menuSelected
|
|
72
|
+
: option.color || theme.colors.menuNormal, bold: true },
|
|
71
73
|
actualIndex === selectedIndex ? '❯ ' : ' ',
|
|
72
74
|
option.label)));
|
|
73
75
|
}),
|
|
74
76
|
hasMoreBelow && (React.createElement(Box, null,
|
|
75
|
-
React.createElement(Text, { color:
|
|
77
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true },
|
|
76
78
|
"\u2193 +",
|
|
77
79
|
moreBelowCount,
|
|
78
80
|
" more below")))));
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { useTheme } from '../contexts/ThemeContext.js';
|
|
3
4
|
export default function PendingMessages({ pendingMessages }) {
|
|
5
|
+
const { theme } = useTheme();
|
|
4
6
|
if (pendingMessages.length === 0) {
|
|
5
7
|
return null;
|
|
6
8
|
}
|
|
7
|
-
return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor:
|
|
8
|
-
React.createElement(Text, { color:
|
|
9
|
+
return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.colors.warning, paddingX: 1 },
|
|
10
|
+
React.createElement(Text, { color: theme.colors.warning, bold: true },
|
|
9
11
|
"\u2B11 Pending Messages (",
|
|
10
12
|
pendingMessages.length,
|
|
11
13
|
")"),
|
|
@@ -15,13 +17,13 @@ export default function PendingMessages({ pendingMessages }) {
|
|
|
15
17
|
index + 1,
|
|
16
18
|
"."),
|
|
17
19
|
React.createElement(Box, { marginLeft: 1 },
|
|
18
|
-
React.createElement(Text, { color:
|
|
20
|
+
React.createElement(Text, { color: theme.colors.menuSecondary }, message.text.length > 60 ? `${message.text.substring(0, 60)}...` : message.text))),
|
|
19
21
|
message.images && message.images.length > 0 && (React.createElement(Box, { marginLeft: 3 },
|
|
20
|
-
React.createElement(Text, { color:
|
|
22
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true },
|
|
21
23
|
"\u2514\u2500 ",
|
|
22
24
|
message.images.length,
|
|
23
25
|
" image",
|
|
24
26
|
message.images.length > 1 ? 's' : '',
|
|
25
27
|
" attached")))))),
|
|
26
|
-
React.createElement(Text, { color:
|
|
28
|
+
React.createElement(Text, { color: theme.colors.warning, dimColor: true }, "Will be sent after tool execution completes")));
|
|
27
29
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React, { memo, useMemo } from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { Alert } from '@inkjs/ui';
|
|
4
|
+
import { useTheme } from '../contexts/ThemeContext.js';
|
|
4
5
|
const TodoPickerPanel = memo(({ todos, selectedIndex, selectedTodos, visible, maxHeight, isLoading = false, searchQuery = '', totalCount = 0, }) => {
|
|
6
|
+
const { theme } = useTheme();
|
|
5
7
|
// Fixed maximum display items to prevent rendering issues
|
|
6
8
|
const MAX_DISPLAY_ITEMS = 5;
|
|
7
9
|
const effectiveMaxItems = maxHeight
|
|
@@ -39,7 +41,7 @@ const TodoPickerPanel = memo(({ todos, selectedIndex, selectedTodos, visible, ma
|
|
|
39
41
|
React.createElement(Box, { width: "100%" },
|
|
40
42
|
React.createElement(Box, { flexDirection: "column", width: "100%" },
|
|
41
43
|
React.createElement(Box, null,
|
|
42
|
-
React.createElement(Text, { color:
|
|
44
|
+
React.createElement(Text, { color: theme.colors.warning, bold: true }, "TODO Selection")),
|
|
43
45
|
React.createElement(Box, { marginTop: 1 },
|
|
44
46
|
React.createElement(Alert, { variant: "info" }, "Scanning project for TODO comments..."))))));
|
|
45
47
|
}
|
|
@@ -49,7 +51,7 @@ const TodoPickerPanel = memo(({ todos, selectedIndex, selectedTodos, visible, ma
|
|
|
49
51
|
React.createElement(Box, { width: "100%" },
|
|
50
52
|
React.createElement(Box, { flexDirection: "column", width: "100%" },
|
|
51
53
|
React.createElement(Box, null,
|
|
52
|
-
React.createElement(Text, { color:
|
|
54
|
+
React.createElement(Text, { color: theme.colors.warning, bold: true }, "TODO Selection")),
|
|
53
55
|
React.createElement(Box, { marginTop: 1 },
|
|
54
56
|
React.createElement(Alert, { variant: "info" }, "No TODO comments found in the project"))))));
|
|
55
57
|
}
|
|
@@ -59,7 +61,7 @@ const TodoPickerPanel = memo(({ todos, selectedIndex, selectedTodos, visible, ma
|
|
|
59
61
|
React.createElement(Box, { width: "100%" },
|
|
60
62
|
React.createElement(Box, { flexDirection: "column", width: "100%" },
|
|
61
63
|
React.createElement(Box, null,
|
|
62
|
-
React.createElement(Text, { color:
|
|
64
|
+
React.createElement(Text, { color: theme.colors.warning, bold: true }, "TODO Selection")),
|
|
63
65
|
React.createElement(Box, { marginTop: 1 },
|
|
64
66
|
React.createElement(Alert, { variant: "warning" },
|
|
65
67
|
"No TODOs match \"",
|
|
@@ -68,13 +70,13 @@ const TodoPickerPanel = memo(({ todos, selectedIndex, selectedTodos, visible, ma
|
|
|
68
70
|
totalCount,
|
|
69
71
|
")")),
|
|
70
72
|
React.createElement(Box, { marginTop: 1 },
|
|
71
|
-
React.createElement(Text, { color:
|
|
73
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, "Type to filter \u00B7 Backspace to clear search"))))));
|
|
72
74
|
}
|
|
73
75
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
74
76
|
React.createElement(Box, { width: "100%" },
|
|
75
77
|
React.createElement(Box, { flexDirection: "column", width: "100%" },
|
|
76
78
|
React.createElement(Box, null,
|
|
77
|
-
React.createElement(Text, { color:
|
|
79
|
+
React.createElement(Text, { color: theme.colors.warning, bold: true },
|
|
78
80
|
"Select TODOs",
|
|
79
81
|
' ',
|
|
80
82
|
todos.length > effectiveMaxItems &&
|
|
@@ -84,14 +86,14 @@ const TodoPickerPanel = memo(({ todos, selectedIndex, selectedTodos, visible, ma
|
|
|
84
86
|
totalCount > todos.length &&
|
|
85
87
|
` (${todos.length}/${totalCount})`)),
|
|
86
88
|
React.createElement(Box, { marginTop: 1 },
|
|
87
|
-
React.createElement(Text, { color:
|
|
89
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, searchQuery
|
|
88
90
|
? 'Type to filter · Backspace to clear · Space: toggle · Enter: confirm'
|
|
89
91
|
: 'Type to search · Space: toggle · Enter: confirm · Esc: cancel')),
|
|
90
92
|
displayedTodos.map((todo, index) => {
|
|
91
93
|
const isSelected = index === displayedSelectedIndex;
|
|
92
94
|
const isChecked = selectedTodos.has(todo.id);
|
|
93
95
|
return (React.createElement(Box, { key: todo.id, flexDirection: "column", width: "100%" },
|
|
94
|
-
React.createElement(Text, { color: isSelected ?
|
|
96
|
+
React.createElement(Text, { color: isSelected ? theme.colors.success : theme.colors.menuSecondary, bold: true },
|
|
95
97
|
isSelected ? '❯ ' : ' ',
|
|
96
98
|
isChecked ? '[✓]' : '[ ]',
|
|
97
99
|
" ",
|
|
@@ -99,17 +101,17 @@ const TodoPickerPanel = memo(({ todos, selectedIndex, selectedTodos, visible, ma
|
|
|
99
101
|
":",
|
|
100
102
|
todo.line),
|
|
101
103
|
React.createElement(Box, { marginLeft: 5 },
|
|
102
|
-
React.createElement(Text, { color: isSelected ?
|
|
104
|
+
React.createElement(Text, { color: isSelected ? theme.colors.success : theme.colors.menuSecondary, dimColor: !isSelected },
|
|
103
105
|
"\u2514\u2500 ",
|
|
104
106
|
todo.content))));
|
|
105
107
|
}),
|
|
106
108
|
todos.length > effectiveMaxItems && (React.createElement(Box, { marginTop: 1 },
|
|
107
|
-
React.createElement(Text, { color:
|
|
109
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true },
|
|
108
110
|
"\u2191\u2193 to scroll \u00B7 ",
|
|
109
111
|
todos.length - effectiveMaxItems,
|
|
110
112
|
" more hidden"))),
|
|
111
113
|
selectedTodos.size > 0 && (React.createElement(Box, { marginTop: 1 },
|
|
112
|
-
React.createElement(Text, { color:
|
|
114
|
+
React.createElement(Text, { color: theme.colors.menuInfo },
|
|
113
115
|
selectedTodos.size,
|
|
114
116
|
" TODO(s) selected")))))));
|
|
115
117
|
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { useTheme } from '../contexts/ThemeContext.js';
|
|
3
4
|
/**
|
|
4
5
|
* TODO Tree 组件 - 显示带复选框的任务树
|
|
5
6
|
*/
|
|
6
7
|
export default function TodoTree({ todos }) {
|
|
8
|
+
const { theme } = useTheme();
|
|
7
9
|
if (todos.length === 0) {
|
|
8
10
|
return null;
|
|
9
11
|
}
|
|
@@ -30,9 +32,9 @@ export default function TodoTree({ todos }) {
|
|
|
30
32
|
const getStatusColor = (status) => {
|
|
31
33
|
switch (status) {
|
|
32
34
|
case 'completed':
|
|
33
|
-
return
|
|
35
|
+
return theme.colors.success;
|
|
34
36
|
case 'pending':
|
|
35
|
-
return
|
|
37
|
+
return theme.colors.menuSecondary;
|
|
36
38
|
}
|
|
37
39
|
};
|
|
38
40
|
const renderTodo = (todo, depth = 0) => {
|
|
@@ -49,10 +51,10 @@ export default function TodoTree({ todos }) {
|
|
|
49
51
|
todo.content)),
|
|
50
52
|
children.map(child => renderTodo(child, depth + 1))));
|
|
51
53
|
};
|
|
52
|
-
return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor:
|
|
54
|
+
return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.colors.menuInfo, paddingX: 1, marginBottom: 1 },
|
|
53
55
|
React.createElement(Box, { marginBottom: 0 },
|
|
54
|
-
React.createElement(Text, { bold: true, color:
|
|
56
|
+
React.createElement(Text, { bold: true, color: theme.colors.menuInfo }, "TODO List")),
|
|
55
57
|
rootTodos.map(todo => renderTodo(todo)),
|
|
56
58
|
React.createElement(Box, { marginTop: 0 },
|
|
57
|
-
React.createElement(Text, { dimColor: true, color:
|
|
59
|
+
React.createElement(Text, { dimColor: true, color: theme.colors.menuSecondary }, "[ ] Pending \u00B7 [\u2713] Completed"))));
|
|
58
60
|
}
|
|
@@ -3,6 +3,7 @@ import { Box, Text } from 'ink';
|
|
|
3
3
|
import TextInput from 'ink-text-input';
|
|
4
4
|
import SelectInput from 'ink-select-input';
|
|
5
5
|
import { isSensitiveCommand } from '../../utils/sensitiveCommandManager.js';
|
|
6
|
+
import { useTheme } from '../contexts/ThemeContext.js';
|
|
6
7
|
// Helper function to format argument values with truncation
|
|
7
8
|
function formatArgumentValue(value, maxLength = 100) {
|
|
8
9
|
if (value === null || value === undefined) {
|
|
@@ -41,6 +42,7 @@ function formatArgumentsAsTree(args, toolName) {
|
|
|
41
42
|
}));
|
|
42
43
|
}
|
|
43
44
|
export default function ToolConfirmation({ toolName, toolArguments, allTools, onConfirm, }) {
|
|
45
|
+
const { theme } = useTheme();
|
|
44
46
|
const [hasSelected, setHasSelected] = useState(false);
|
|
45
47
|
const [showRejectInput, setShowRejectInput] = useState(false);
|
|
46
48
|
const [rejectReason, setRejectReason] = useState('');
|
|
@@ -135,18 +137,18 @@ export default function ToolConfirmation({ toolName, toolArguments, allTools, on
|
|
|
135
137
|
onConfirm({ type: 'reject_with_reply', reason: rejectReason.trim() });
|
|
136
138
|
}
|
|
137
139
|
};
|
|
138
|
-
return (React.createElement(Box, { flexDirection: "column", marginX: 1, marginY: 1, borderStyle: 'round', borderColor:
|
|
140
|
+
return (React.createElement(Box, { flexDirection: "column", marginX: 1, marginY: 1, borderStyle: 'round', borderColor: theme.colors.warning, paddingX: 1 },
|
|
139
141
|
React.createElement(Box, { marginBottom: 1 },
|
|
140
|
-
React.createElement(Text, { bold: true, color:
|
|
142
|
+
React.createElement(Text, { bold: true, color: theme.colors.warning }, "[Tool Confirmation]")),
|
|
141
143
|
!formattedAllTools && (React.createElement(React.Fragment, null,
|
|
142
144
|
React.createElement(Box, { marginBottom: 1 },
|
|
143
145
|
React.createElement(Text, null,
|
|
144
146
|
"Tool:",
|
|
145
147
|
' ',
|
|
146
|
-
React.createElement(Text, { bold: true, color:
|
|
148
|
+
React.createElement(Text, { bold: true, color: theme.colors.menuInfo }, toolName))),
|
|
147
149
|
sensitiveCommandCheck.isSensitive && (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
148
150
|
React.createElement(Box, { marginBottom: 1 },
|
|
149
|
-
React.createElement(Text, { bold: true, color:
|
|
151
|
+
React.createElement(Text, { bold: true, color: theme.colors.error }, "SENSITIVE COMMAND DETECTED")),
|
|
150
152
|
React.createElement(Box, { flexDirection: "column", gap: 0 },
|
|
151
153
|
React.createElement(Box, null,
|
|
152
154
|
React.createElement(Text, { dimColor: true }, "Pattern: "),
|
|
@@ -155,11 +157,11 @@ export default function ToolConfirmation({ toolName, toolArguments, allTools, on
|
|
|
155
157
|
React.createElement(Text, { dimColor: true }, "Reason: "),
|
|
156
158
|
React.createElement(Text, { color: "white" }, sensitiveCommandCheck.matchedCommand?.description))),
|
|
157
159
|
React.createElement(Box, { marginTop: 1, paddingX: 1, paddingY: 0 },
|
|
158
|
-
React.createElement(Text, { color:
|
|
160
|
+
React.createElement(Text, { color: theme.colors.warning, italic: true }, "This command requires confirmation even in YOLO/Always-Approved mode")))),
|
|
159
161
|
formattedArgs && formattedArgs.length > 0 && (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
160
162
|
React.createElement(Text, { dimColor: true }, "Arguments:"),
|
|
161
163
|
formattedArgs.map((arg, index) => (React.createElement(Box, { key: index, flexDirection: "column" },
|
|
162
|
-
React.createElement(Text, { color:
|
|
164
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true },
|
|
163
165
|
arg.isLast ? '└─' : '├─',
|
|
164
166
|
" ",
|
|
165
167
|
arg.key,
|
|
@@ -171,15 +173,15 @@ export default function ToolConfirmation({ toolName, toolArguments, allTools, on
|
|
|
171
173
|
React.createElement(Text, null,
|
|
172
174
|
"Tools:",
|
|
173
175
|
' ',
|
|
174
|
-
React.createElement(Text, { bold: true, color:
|
|
176
|
+
React.createElement(Text, { bold: true, color: theme.colors.menuInfo },
|
|
175
177
|
formattedAllTools.length,
|
|
176
178
|
" tools in parallel"))),
|
|
177
179
|
formattedAllTools.map((tool, toolIndex) => (React.createElement(Box, { key: toolIndex, flexDirection: "column", marginBottom: toolIndex < formattedAllTools.length - 1 ? 1 : 0 },
|
|
178
|
-
React.createElement(Text, { color:
|
|
180
|
+
React.createElement(Text, { color: theme.colors.menuInfo, bold: true },
|
|
179
181
|
toolIndex + 1,
|
|
180
182
|
". ",
|
|
181
183
|
tool.name),
|
|
182
|
-
tool.args.length > 0 && (React.createElement(Box, { flexDirection: "column", paddingLeft: 2 }, tool.args.map((arg, argIndex) => (React.createElement(Text, { key: argIndex, color:
|
|
184
|
+
tool.args.length > 0 && (React.createElement(Box, { flexDirection: "column", paddingLeft: 2 }, tool.args.map((arg, argIndex) => (React.createElement(Text, { key: argIndex, color: theme.colors.menuSecondary, dimColor: true },
|
|
183
185
|
arg.isLast ? '└─' : '├─',
|
|
184
186
|
" ",
|
|
185
187
|
arg.key,
|
|
@@ -191,12 +193,12 @@ export default function ToolConfirmation({ toolName, toolArguments, allTools, on
|
|
|
191
193
|
!hasSelected && !showRejectInput && (React.createElement(SelectInput, { items: items, onSelect: handleSelect })),
|
|
192
194
|
showRejectInput && !hasSelected && (React.createElement(Box, { flexDirection: "column" },
|
|
193
195
|
React.createElement(Box, { marginBottom: 1 },
|
|
194
|
-
React.createElement(Text, { color:
|
|
196
|
+
React.createElement(Text, { color: theme.colors.warning }, "Enter rejection reason:")),
|
|
195
197
|
React.createElement(Box, { marginBottom: 1 },
|
|
196
|
-
React.createElement(Text, { color:
|
|
198
|
+
React.createElement(Text, { color: theme.colors.menuInfo }, "> "),
|
|
197
199
|
React.createElement(TextInput, { value: rejectReason, onChange: setRejectReason, onSubmit: handleRejectReasonSubmit })),
|
|
198
200
|
React.createElement(Box, null,
|
|
199
201
|
React.createElement(Text, { dimColor: true }, "Press Enter to submit")))),
|
|
200
202
|
hasSelected && (React.createElement(Box, null,
|
|
201
|
-
React.createElement(Text, { color:
|
|
203
|
+
React.createElement(Text, { color: theme.colors.success }, "Confirmed")))));
|
|
202
204
|
}
|
|
@@ -305,14 +305,28 @@ function renderTodoPreview(_toolName, data, _maxLines) {
|
|
|
305
305
|
let todoData = data;
|
|
306
306
|
// If data has content array (MCP format), extract the text
|
|
307
307
|
if (data.content && Array.isArray(data.content) && data.content[0]?.text) {
|
|
308
|
+
const textContent = data.content[0].text;
|
|
309
|
+
// Skip parsing if it's a plain message string
|
|
310
|
+
if (textContent === 'No TODO list found' || textContent === 'TODO item not found') {
|
|
311
|
+
return (React.createElement(Box, { marginLeft: 2 },
|
|
312
|
+
React.createElement(Text, { color: "gray", dimColor: true },
|
|
313
|
+
"\u2514\u2500 ",
|
|
314
|
+
textContent)));
|
|
315
|
+
}
|
|
316
|
+
// Try to parse JSON
|
|
308
317
|
try {
|
|
309
|
-
todoData = JSON.parse(
|
|
318
|
+
todoData = JSON.parse(textContent);
|
|
310
319
|
}
|
|
311
320
|
catch (e) {
|
|
312
|
-
// If parsing fails,
|
|
321
|
+
// If parsing fails, show the raw text
|
|
322
|
+
return (React.createElement(Box, { marginLeft: 2 },
|
|
323
|
+
React.createElement(Text, { color: "gray", dimColor: true },
|
|
324
|
+
"\u2514\u2500 ",
|
|
325
|
+
textContent)));
|
|
313
326
|
}
|
|
314
327
|
}
|
|
315
|
-
if
|
|
328
|
+
// Check if we have valid todo data
|
|
329
|
+
if (!todoData.todos || !Array.isArray(todoData.todos)) {
|
|
316
330
|
return (React.createElement(Box, { marginLeft: 2 },
|
|
317
331
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
318
332
|
"\u2514\u2500 ",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
import { ThemeType, Theme } from '../themes/index.js';
|
|
3
|
+
interface ThemeContextType {
|
|
4
|
+
theme: Theme;
|
|
5
|
+
themeType: ThemeType;
|
|
6
|
+
setThemeType: (type: ThemeType) => void;
|
|
7
|
+
}
|
|
8
|
+
interface ThemeProviderProps {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
export declare function ThemeProvider({ children }: ThemeProviderProps): React.JSX.Element;
|
|
12
|
+
export declare function useTheme(): ThemeContextType;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState, } from 'react';
|
|
2
|
+
import { themes } from '../themes/index.js';
|
|
3
|
+
import { getCurrentTheme, setCurrentTheme, } from '../../utils/themeConfig.js';
|
|
4
|
+
const ThemeContext = createContext(undefined);
|
|
5
|
+
export function ThemeProvider({ children }) {
|
|
6
|
+
const [themeType, setThemeTypeState] = useState(() => {
|
|
7
|
+
// Load initial theme from config
|
|
8
|
+
return getCurrentTheme();
|
|
9
|
+
});
|
|
10
|
+
const setThemeType = (type) => {
|
|
11
|
+
setThemeTypeState(type);
|
|
12
|
+
// Persist to config file
|
|
13
|
+
setCurrentTheme(type);
|
|
14
|
+
};
|
|
15
|
+
const value = {
|
|
16
|
+
theme: themes[themeType],
|
|
17
|
+
themeType,
|
|
18
|
+
setThemeType,
|
|
19
|
+
};
|
|
20
|
+
return (React.createElement(ThemeContext.Provider, { value: value }, children));
|
|
21
|
+
}
|
|
22
|
+
export function useTheme() {
|
|
23
|
+
const context = useContext(ThemeContext);
|
|
24
|
+
if (!context) {
|
|
25
|
+
throw new Error('useTheme must be used within a ThemeProvider');
|
|
26
|
+
}
|
|
27
|
+
return context;
|
|
28
|
+
}
|
|
@@ -4,6 +4,7 @@ import Spinner from 'ink-spinner';
|
|
|
4
4
|
import Gradient from 'ink-gradient';
|
|
5
5
|
import ansiEscapes from 'ansi-escapes';
|
|
6
6
|
import { useI18n } from '../../i18n/I18nContext.js';
|
|
7
|
+
import { useTheme } from '../contexts/ThemeContext.js';
|
|
7
8
|
import ChatInput from '../components/ChatInput.js';
|
|
8
9
|
import PendingMessages from '../components/PendingMessages.js';
|
|
9
10
|
import MCPInfoScreen from '../components/MCPInfoScreen.js';
|
|
@@ -56,6 +57,7 @@ import '../../utils/commands/todoPicker.js';
|
|
|
56
57
|
import '../../utils/commands/help.js';
|
|
57
58
|
export default function ChatScreen({ skipWelcome }) {
|
|
58
59
|
const { t } = useI18n();
|
|
60
|
+
const { theme } = useTheme();
|
|
59
61
|
const [messages, setMessages] = useState([]);
|
|
60
62
|
const [isSaving] = useState(false);
|
|
61
63
|
const [pendingMessages, setPendingMessages] = useState([]);
|
|
@@ -1112,7 +1114,7 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
1112
1114
|
.replace('{current}', terminalHeight.toString())
|
|
1113
1115
|
.replace('{required}', MIN_TERMINAL_HEIGHT.toString()))),
|
|
1114
1116
|
React.createElement(Box, { marginTop: 1 },
|
|
1115
|
-
React.createElement(Text, { color:
|
|
1117
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, t.chatScreen.terminalMinHeight))));
|
|
1116
1118
|
}
|
|
1117
1119
|
return (React.createElement(Box, { flexDirection: "column", height: "100%", width: terminalWidth },
|
|
1118
1120
|
React.createElement(Static, { key: remountKey, items: [
|
|
@@ -1136,7 +1138,7 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
1136
1138
|
const pasteKey = process.platform === 'darwin' ? 'Ctrl+V' : 'Alt+V';
|
|
1137
1139
|
return `• ${t.chatScreen.headerShortcuts.replace('{pasteKey}', pasteKey)}`;
|
|
1138
1140
|
})()),
|
|
1139
|
-
React.createElement(Text, { color:
|
|
1141
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true },
|
|
1140
1142
|
"\u2022",
|
|
1141
1143
|
' ',
|
|
1142
1144
|
t.chatScreen.headerWorkingDirectory.replace('{directory}', workingDirectory))))),
|
|
@@ -1199,12 +1201,12 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
1199
1201
|
}
|
|
1200
1202
|
return (React.createElement(Box, { key: `msg-${index}`, marginTop: index > 0 && !shouldShowParallelIndicator ? 1 : 0, marginBottom: isLastMessage ? 1 : 0, paddingX: 1, flexDirection: "column", width: terminalWidth },
|
|
1201
1203
|
isFirstInGroup && (React.createElement(Box, { marginBottom: 0 },
|
|
1202
|
-
React.createElement(Text, { color:
|
|
1204
|
+
React.createElement(Text, { color: theme.colors.menuInfo, dimColor: true }, "\u250C\u2500 Parallel execution"))),
|
|
1203
1205
|
React.createElement(Box, null,
|
|
1204
1206
|
React.createElement(Text, { color: message.role === 'user'
|
|
1205
1207
|
? 'green'
|
|
1206
1208
|
: message.role === 'command'
|
|
1207
|
-
?
|
|
1209
|
+
? theme.colors.menuSecondary
|
|
1208
1210
|
: toolStatusColor, bold: true },
|
|
1209
1211
|
shouldShowParallelIndicator && !isFirstInGroup
|
|
1210
1212
|
? '│'
|
|
@@ -1215,7 +1217,7 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
1215
1217
|
? '⌘'
|
|
1216
1218
|
: '❆'),
|
|
1217
1219
|
React.createElement(Box, { marginLeft: 1, flexDirection: "column" }, message.role === 'command' ? (React.createElement(React.Fragment, null,
|
|
1218
|
-
React.createElement(Text, { color:
|
|
1220
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true },
|
|
1219
1221
|
"\u2514\u2500 ",
|
|
1220
1222
|
message.commandName),
|
|
1221
1223
|
message.content && (React.createElement(Text, { color: "white" }, message.content)))) : (React.createElement(React.Fragment, null,
|
|
@@ -1225,7 +1227,7 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
1225
1227
|
? 'yellow'
|
|
1226
1228
|
: message.content.startsWith('✓')
|
|
1227
1229
|
? 'green'
|
|
1228
|
-
: 'red', backgroundColor: message.role === 'user' ?
|
|
1230
|
+
: 'red', backgroundColor: message.role === 'user' ? theme.colors.border : undefined }, message.content || ' ')) : (React.createElement(MarkdownRenderer, { content: message.content || ' ' })),
|
|
1229
1231
|
message.subAgentUsage &&
|
|
1230
1232
|
(() => {
|
|
1231
1233
|
const formatTokens = (num) => {
|
|
@@ -1233,7 +1235,7 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
1233
1235
|
return `${(num / 1000).toFixed(1)}K`;
|
|
1234
1236
|
return num.toString();
|
|
1235
1237
|
};
|
|
1236
|
-
return (React.createElement(Text, { color:
|
|
1238
|
+
return (React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true },
|
|
1237
1239
|
"\u2514\u2500 Usage: In=",
|
|
1238
1240
|
formatTokens(message.subAgentUsage.inputTokens),
|
|
1239
1241
|
", Out=",
|
|
@@ -1251,7 +1253,7 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
1251
1253
|
message.toolDisplay &&
|
|
1252
1254
|
message.toolDisplay.args.length > 0 &&
|
|
1253
1255
|
// Hide tool arguments for sub-agent internal tools
|
|
1254
|
-
!message.subAgentInternal && (React.createElement(Box, { flexDirection: "column" }, message.toolDisplay.args.map((arg, argIndex) => (React.createElement(Text, { key: argIndex, color:
|
|
1256
|
+
!message.subAgentInternal && (React.createElement(Box, { flexDirection: "column" }, message.toolDisplay.args.map((arg, argIndex) => (React.createElement(Text, { key: argIndex, color: theme.colors.menuSecondary, dimColor: true },
|
|
1255
1257
|
arg.isLast ? '└─' : '├─',
|
|
1256
1258
|
" ",
|
|
1257
1259
|
arg.key,
|
|
@@ -1311,13 +1313,13 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
1311
1313
|
message.content.includes('⚇✗')) &&
|
|
1312
1314
|
message.content.includes('Tool execution rejected by user:') && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
1313
1315
|
React.createElement(Text, { color: "yellow", dimColor: true }, "Rejection reason:"),
|
|
1314
|
-
React.createElement(Text, { color:
|
|
1316
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true },
|
|
1315
1317
|
"\u2514\u2500",
|
|
1316
1318
|
' ',
|
|
1317
1319
|
message.content
|
|
1318
1320
|
.split('Tool execution rejected by user:')[1]
|
|
1319
1321
|
?.trim() || 'No reason provided'))),
|
|
1320
|
-
message.files && message.files.length > 0 && (React.createElement(Box, { flexDirection: "column" }, message.files.map((file, fileIndex) => (React.createElement(Text, { key: fileIndex, color:
|
|
1322
|
+
message.files && message.files.length > 0 && (React.createElement(Box, { flexDirection: "column" }, message.files.map((file, fileIndex) => (React.createElement(Text, { key: fileIndex, color: theme.colors.menuSecondary, dimColor: true },
|
|
1321
1323
|
"\u2514\u2500 ",
|
|
1322
1324
|
file.path,
|
|
1323
1325
|
file.exists
|
|
@@ -1325,17 +1327,17 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
1325
1327
|
: ' (file not found)'))))),
|
|
1326
1328
|
message.role === 'user' &&
|
|
1327
1329
|
message.images &&
|
|
1328
|
-
message.images.length > 0 && (React.createElement(Box, { marginTop: 1, flexDirection: "column" }, message.images.map((_image, imageIndex) => (React.createElement(Text, { key: imageIndex, color:
|
|
1330
|
+
message.images.length > 0 && (React.createElement(Box, { marginTop: 1, flexDirection: "column" }, message.images.map((_image, imageIndex) => (React.createElement(Text, { key: imageIndex, color: theme.colors.menuSecondary, dimColor: true },
|
|
1329
1331
|
"\u2514\u2500 [image #",
|
|
1330
1332
|
imageIndex + 1,
|
|
1331
1333
|
"]"))))),
|
|
1332
1334
|
message.discontinued && (React.createElement(Text, { color: "red", bold: true }, "\u2514\u2500 user discontinue")))))),
|
|
1333
1335
|
isLastInGroup && (React.createElement(Box, { marginTop: 0 },
|
|
1334
|
-
React.createElement(Text, { color:
|
|
1336
|
+
React.createElement(Text, { color: theme.colors.menuInfo, dimColor: true }, "\u2514\u2500 End parallel execution")))));
|
|
1335
1337
|
}),
|
|
1336
1338
|
] }, item => item),
|
|
1337
1339
|
(streamingState.isStreaming || isSaving) && !pendingToolConfirmation && (React.createElement(Box, { marginBottom: 1, paddingX: 1, width: terminalWidth },
|
|
1338
|
-
React.createElement(Text, { color: [
|
|
1340
|
+
React.createElement(Text, { color: [theme.colors.menuInfo, theme.colors.success, theme.colors.menuSelected, theme.colors.menuInfo, theme.colors.menuSecondary][streamingState.animationFrame], bold: true }, "\u2746"),
|
|
1339
1341
|
React.createElement(Box, { marginLeft: 1, marginBottom: 1, flexDirection: "column" }, streamingState.isStreaming ? (React.createElement(React.Fragment, null, streamingState.retryStatus &&
|
|
1340
1342
|
streamingState.retryStatus.isRetrying ? (
|
|
1341
1343
|
// Retry status display - hide "Thinking" and show retry info
|
|
@@ -1365,9 +1367,9 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
1365
1367
|
"/",
|
|
1366
1368
|
streamingState.codebaseSearchStatus.maxAttempts,
|
|
1367
1369
|
")"),
|
|
1368
|
-
React.createElement(Text, { color:
|
|
1370
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, streamingState.codebaseSearchStatus.message))) : (
|
|
1369
1371
|
// Normal thinking status
|
|
1370
|
-
React.createElement(Text, { color:
|
|
1372
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true },
|
|
1371
1373
|
React.createElement(ShimmerText, { text: streamingState.isReasoning
|
|
1372
1374
|
? t.chatScreen.statusDeepThinking
|
|
1373
1375
|
: streamingState.streamTokenCount > 0
|
|
@@ -1385,7 +1387,7 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
1385
1387
|
: streamingState.streamTokenCount,
|
|
1386
1388
|
' ',
|
|
1387
1389
|
"tokens"),
|
|
1388
|
-
")")))) : (React.createElement(Text, { color:
|
|
1390
|
+
")")))) : (React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, t.chatScreen.sessionCreating))))),
|
|
1389
1391
|
React.createElement(Box, { paddingX: 1, width: terminalWidth },
|
|
1390
1392
|
React.createElement(PendingMessages, { pendingMessages: pendingMessages })),
|
|
1391
1393
|
pendingToolConfirmation && (React.createElement(ToolConfirmation, { toolName: pendingToolConfirmation.batchToolNames ||
|
|
@@ -1397,11 +1399,11 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
1397
1399
|
showMcpPanel && (React.createElement(Box, { paddingX: 1, flexDirection: "column", width: terminalWidth },
|
|
1398
1400
|
React.createElement(MCPInfoPanel, null),
|
|
1399
1401
|
React.createElement(Box, { marginTop: 1 },
|
|
1400
|
-
React.createElement(Text, { color:
|
|
1402
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, t.chatScreen.pressEscToClose)))),
|
|
1401
1403
|
showUsagePanel && (React.createElement(Box, { paddingX: 1, flexDirection: "column", width: terminalWidth },
|
|
1402
1404
|
React.createElement(UsagePanel, null),
|
|
1403
1405
|
React.createElement(Box, { marginTop: 1 },
|
|
1404
|
-
React.createElement(Text, { color:
|
|
1406
|
+
React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, t.chatScreen.pressEscToClose)))),
|
|
1405
1407
|
showHelpPanel && (React.createElement(Box, { paddingX: 1, flexDirection: "column", width: terminalWidth },
|
|
1406
1408
|
React.createElement(HelpPanel, null))),
|
|
1407
1409
|
snapshotState.pendingRollback && (React.createElement(FileRollbackConfirmation, { fileCount: snapshotState.pendingRollback.fileCount, filePaths: snapshotState.pendingRollback.filePaths || [], onConfirm: handleRollbackConfirm })),
|
|
@@ -1428,7 +1430,7 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
1428
1430
|
? 'green'
|
|
1429
1431
|
: vscodeState.vscodeConnectionStatus === 'error'
|
|
1430
1432
|
? 'red'
|
|
1431
|
-
:
|
|
1433
|
+
: theme.colors.menuSecondary, dimColor: vscodeState.vscodeConnectionStatus !== 'error' },
|
|
1432
1434
|
"\u25CF",
|
|
1433
1435
|
' ',
|
|
1434
1436
|
vscodeState.vscodeConnectionStatus === 'connecting'
|