snow-ai 0.4.14 → 0.4.16

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.
@@ -1,10 +1,11 @@
1
- import React, { useCallback, useEffect, useRef, useMemo } from 'react';
1
+ import React, { useCallback, useEffect, useRef, useMemo, lazy, Suspense } from 'react';
2
2
  import { Box, Text } from 'ink';
3
3
  import { cpSlice } from '../../utils/textUtils.js';
4
- import CommandPanel from './CommandPanel.js';
5
- import FileList from './FileList.js';
6
- import AgentPickerPanel from './AgentPickerPanel.js';
7
- import TodoPickerPanel from './TodoPickerPanel.js';
4
+ // Lazy load panel components to reduce initial bundle size
5
+ const CommandPanel = lazy(() => import('./CommandPanel.js'));
6
+ const FileList = lazy(() => import('./FileList.js'));
7
+ const AgentPickerPanel = lazy(() => import('./AgentPickerPanel.js'));
8
+ const TodoPickerPanel = lazy(() => import('./TodoPickerPanel.js'));
8
9
  import { useInputBuffer } from '../../hooks/useInputBuffer.js';
9
10
  import { useCommandPanel } from '../../hooks/useCommandPanel.js';
10
11
  import { useFilePicker } from '../../hooks/useFilePicker.js';
@@ -309,11 +310,15 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
309
310
  ? t.chatScreen.contentSearchHint
310
311
  : t.chatScreen.fileSearchHint
311
312
  : ''))) : null,
312
- React.createElement(CommandPanel, { commands: getFilteredCommands(), selectedIndex: commandSelectedIndex, query: buffer.getFullText().slice(1), visible: showCommands, isProcessing: commandPanelIsProcessing }),
313
+ React.createElement(Suspense, { fallback: null },
314
+ React.createElement(CommandPanel, { commands: getFilteredCommands(), selectedIndex: commandSelectedIndex, query: buffer.getFullText().slice(1), visible: showCommands, isProcessing: commandPanelIsProcessing })),
313
315
  React.createElement(Box, null,
314
- React.createElement(FileList, { ref: fileListRef, query: fileQuery, selectedIndex: fileSelectedIndex, visible: showFilePicker, maxItems: 10, rootPath: process.cwd(), onFilteredCountChange: handleFilteredCountChange, searchMode: searchMode })),
315
- React.createElement(AgentPickerPanel, { agents: getFilteredAgents(), selectedIndex: agentSelectedIndex, visible: showAgentPicker, maxHeight: 5 }),
316
- React.createElement(TodoPickerPanel, { todos: todos, selectedIndex: todoSelectedIndex, selectedTodos: selectedTodos, visible: showTodoPicker, maxHeight: 5, isLoading: todoIsLoading, searchQuery: todoSearchQuery, totalCount: totalTodoCount }),
316
+ React.createElement(Suspense, { fallback: null },
317
+ React.createElement(FileList, { ref: fileListRef, query: fileQuery, selectedIndex: fileSelectedIndex, visible: showFilePicker, maxItems: 10, rootPath: process.cwd(), onFilteredCountChange: handleFilteredCountChange, searchMode: searchMode }))),
318
+ React.createElement(Suspense, { fallback: null },
319
+ React.createElement(AgentPickerPanel, { agents: getFilteredAgents(), selectedIndex: agentSelectedIndex, visible: showAgentPicker, maxHeight: 5 })),
320
+ React.createElement(Suspense, { fallback: null },
321
+ React.createElement(TodoPickerPanel, { todos: todos, selectedIndex: todoSelectedIndex, selectedTodos: selectedTodos, visible: showTodoPicker, maxHeight: 5, isLoading: todoIsLoading, searchQuery: todoSearchQuery, totalCount: totalTodoCount })),
317
322
  yoloMode && (React.createElement(Box, { marginTop: 1 },
318
323
  React.createElement(Text, { color: theme.colors.warning, dimColor: true }, t.chatScreen.yoloModeActive))),
319
324
  contextUsage && (React.createElement(Box, { marginTop: 1 },
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useRef } from 'react';
1
+ import React, { useState, useEffect, useRef, lazy, Suspense } from 'react';
2
2
  import { Box, Text, useInput, Static, useStdout } from 'ink';
3
3
  import Spinner from 'ink-spinner';
4
4
  import Gradient from 'ink-gradient';
@@ -7,17 +7,18 @@ import { useI18n } from '../../i18n/I18nContext.js';
7
7
  import { useTheme } from '../contexts/ThemeContext.js';
8
8
  import ChatInput from '../components/ChatInput.js';
9
9
  import PendingMessages from '../components/PendingMessages.js';
10
- import MCPInfoScreen from '../components/MCPInfoScreen.js';
11
- import MCPInfoPanel from '../components/MCPInfoPanel.js';
12
- import SessionListPanel from '../components/SessionListPanel.js';
13
- import UsagePanel from '../components/UsagePanel.js';
14
- import HelpPanel from '../components/HelpPanel.js';
15
10
  import MarkdownRenderer from '../components/MarkdownRenderer.js';
16
11
  import ToolConfirmation from '../components/ToolConfirmation.js';
17
12
  import DiffViewer from '../components/DiffViewer.js';
18
13
  import ToolResultPreview from '../components/ToolResultPreview.js';
19
14
  import FileRollbackConfirmation from '../components/FileRollbackConfirmation.js';
20
15
  import ShimmerText from '../components/ShimmerText.js';
16
+ // Lazy load panel components to reduce initial bundle size
17
+ const MCPInfoScreen = lazy(() => import('../components/MCPInfoScreen.js'));
18
+ const MCPInfoPanel = lazy(() => import('../components/MCPInfoPanel.js'));
19
+ const SessionListPanel = lazy(() => import('../components/SessionListPanel.js'));
20
+ const UsagePanel = lazy(() => import('../components/UsagePanel.js'));
21
+ const HelpPanel = lazy(() => import('../components/HelpPanel.js'));
21
22
  import { getOpenAiConfig } from '../../utils/apiConfig.js';
22
23
  import { sessionManager } from '../../utils/sessionManager.js';
23
24
  import { useSessionSave } from '../../hooks/useSessionSave.js';
@@ -30,7 +31,7 @@ import { useStreamingState } from '../../hooks/useStreamingState.js';
30
31
  import { useCommandHandler } from '../../hooks/useCommandHandler.js';
31
32
  import { useTerminalSize } from '../../hooks/useTerminalSize.js';
32
33
  import { parseAndValidateFileReferences, createMessageWithFileInstructions, } from '../../utils/fileUtils.js';
33
- import { executeCommand } from '../../utils/commandExecutor.js';
34
+ import { vscodeConnection } from '../../utils/vscodeConnection.js';
34
35
  import { convertSessionMessagesToUI } from '../../utils/sessionConverter.js';
35
36
  import { incrementalSnapshotManager } from '../../utils/incrementalSnapshot.js';
36
37
  import { formatElapsedTime } from '../../utils/textUtils.js';
@@ -94,6 +95,8 @@ export default function ChatScreen({ skipWelcome }) {
94
95
  useEffect(() => {
95
96
  pendingMessagesRef.current = pendingMessages;
96
97
  }, [pendingMessages]);
98
+ // Track if commands are loaded
99
+ const [commandsLoaded, setCommandsLoaded] = useState(false);
97
100
  // Load commands dynamically to avoid blocking initial render
98
101
  useEffect(() => {
99
102
  // Use Promise.all to load all commands in parallel
@@ -113,8 +116,14 @@ export default function ChatScreen({ skipWelcome }) {
113
116
  import('../../utils/commands/agent.js'),
114
117
  import('../../utils/commands/todoPicker.js'),
115
118
  import('../../utils/commands/help.js'),
116
- ]).catch(error => {
119
+ ])
120
+ .then(() => {
121
+ setCommandsLoaded(true);
122
+ })
123
+ .catch(error => {
117
124
  console.error('Failed to load commands:', error);
125
+ // Still mark as loaded to allow app to continue
126
+ setCommandsLoaded(true);
118
127
  });
119
128
  }, []);
120
129
  // Auto-start codebase indexing on mount if enabled
@@ -371,6 +380,10 @@ export default function ChatScreen({ skipWelcome }) {
371
380
  processMessage: (message, images, useBasicModel, hideUserMessage) => processMessageRef.current?.(message, images, useBasicModel, hideUserMessage) || Promise.resolve(),
372
381
  });
373
382
  useEffect(() => {
383
+ // Wait for commands to be loaded before attempting auto-connect
384
+ if (!commandsLoaded) {
385
+ return;
386
+ }
374
387
  if (hasAttemptedAutoVscodeConnect.current) {
375
388
  return;
376
389
  }
@@ -379,22 +392,33 @@ export default function ChatScreen({ skipWelcome }) {
379
392
  return;
380
393
  }
381
394
  hasAttemptedAutoVscodeConnect.current = true;
382
- (async () => {
383
- try {
384
- const result = await executeCommand('ide');
385
- await handleCommandExecution('ide', result);
386
- }
387
- catch (error) {
388
- console.error('Failed to auto-connect VSCode:', error);
389
- await handleCommandExecution('ide', {
390
- success: false,
391
- message: error instanceof Error
392
- ? error.message
393
- : 'Failed to start VSCode connection',
394
- });
395
- }
396
- })();
397
- }, [handleCommandExecution, vscodeState.vscodeConnectionStatus]);
395
+ // Auto-connect IDE in background without blocking UI
396
+ // Use setTimeout to defer execution and make it fully async
397
+ const timer = setTimeout(() => {
398
+ // Fire and forget - don't wait for result
399
+ (async () => {
400
+ try {
401
+ // Clean up any existing connection state first (like manual /ide does)
402
+ if (vscodeConnection.isConnected() || vscodeConnection.isClientRunning()) {
403
+ vscodeConnection.stop();
404
+ vscodeConnection.resetReconnectAttempts();
405
+ await new Promise(resolve => setTimeout(resolve, 100));
406
+ }
407
+ // Set connecting status after cleanup
408
+ vscodeState.setVscodeConnectionStatus('connecting');
409
+ // Now try to connect
410
+ await vscodeConnection.start();
411
+ // If we get here, connection succeeded
412
+ // Status will be updated by useVSCodeState hook monitoring
413
+ }
414
+ catch (error) {
415
+ console.error('Background VSCode auto-connect failed:', error);
416
+ // Let useVSCodeState handle the timeout and error state
417
+ }
418
+ })();
419
+ }, 0);
420
+ return () => clearTimeout(timer);
421
+ }, [commandsLoaded, vscodeState]);
398
422
  // Pending messages are now handled inline during tool execution in useConversation
399
423
  // Auto-send pending messages when streaming completely stops (as fallback)
400
424
  useEffect(() => {
@@ -1111,7 +1135,11 @@ export default function ChatScreen({ skipWelcome }) {
1111
1135
  }
1112
1136
  };
1113
1137
  if (showMcpInfo) {
1114
- return (React.createElement(MCPInfoScreen, { onClose: () => setShowMcpInfo(false), panelKey: mcpPanelKey }));
1138
+ return (React.createElement(Suspense, { fallback: React.createElement(Box, null,
1139
+ React.createElement(Text, null,
1140
+ React.createElement(Spinner, { type: "dots" }),
1141
+ " Loading...")) },
1142
+ React.createElement(MCPInfoScreen, { onClose: () => setShowMcpInfo(false), panelKey: mcpPanelKey })));
1115
1143
  }
1116
1144
  // Show warning if terminal is too small
1117
1145
  if (terminalHeight < MIN_TERMINAL_HEIGHT) {
@@ -1404,17 +1432,33 @@ export default function ChatScreen({ skipWelcome }) {
1404
1432
  ? pendingToolConfirmation.tool.function.arguments
1405
1433
  : undefined, allTools: pendingToolConfirmation.allTools, onConfirm: pendingToolConfirmation.resolve })),
1406
1434
  showSessionPanel && (React.createElement(Box, { paddingX: 1, width: terminalWidth },
1407
- React.createElement(SessionListPanel, { onSelectSession: handleSessionPanelSelect, onClose: () => setShowSessionPanel(false) }))),
1435
+ React.createElement(Suspense, { fallback: React.createElement(Box, null,
1436
+ React.createElement(Text, null,
1437
+ React.createElement(Spinner, { type: "dots" }),
1438
+ " Loading...")) },
1439
+ React.createElement(SessionListPanel, { onSelectSession: handleSessionPanelSelect, onClose: () => setShowSessionPanel(false) })))),
1408
1440
  showMcpPanel && (React.createElement(Box, { paddingX: 1, flexDirection: "column", width: terminalWidth },
1409
- React.createElement(MCPInfoPanel, null),
1441
+ React.createElement(Suspense, { fallback: React.createElement(Box, null,
1442
+ React.createElement(Text, null,
1443
+ React.createElement(Spinner, { type: "dots" }),
1444
+ " Loading...")) },
1445
+ React.createElement(MCPInfoPanel, null)),
1410
1446
  React.createElement(Box, { marginTop: 1 },
1411
1447
  React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, t.chatScreen.pressEscToClose)))),
1412
1448
  showUsagePanel && (React.createElement(Box, { paddingX: 1, flexDirection: "column", width: terminalWidth },
1413
- React.createElement(UsagePanel, null),
1449
+ React.createElement(Suspense, { fallback: React.createElement(Box, null,
1450
+ React.createElement(Text, null,
1451
+ React.createElement(Spinner, { type: "dots" }),
1452
+ " Loading...")) },
1453
+ React.createElement(UsagePanel, null)),
1414
1454
  React.createElement(Box, { marginTop: 1 },
1415
1455
  React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, t.chatScreen.pressEscToClose)))),
1416
1456
  showHelpPanel && (React.createElement(Box, { paddingX: 1, flexDirection: "column", width: terminalWidth },
1417
- React.createElement(HelpPanel, null))),
1457
+ React.createElement(Suspense, { fallback: React.createElement(Box, null,
1458
+ React.createElement(Text, null,
1459
+ React.createElement(Spinner, { type: "dots" }),
1460
+ " Loading...")) },
1461
+ React.createElement(HelpPanel, null)))),
1418
1462
  snapshotState.pendingRollback && (React.createElement(FileRollbackConfirmation, { fileCount: snapshotState.pendingRollback.fileCount, filePaths: snapshotState.pendingRollback.filePaths || [], onConfirm: handleRollbackConfirm })),
1419
1463
  !pendingToolConfirmation &&
1420
1464
  !isCompressing &&
@@ -1432,29 +1476,21 @@ export default function ChatScreen({ skipWelcome }) {
1432
1476
  cachedTokens: streamingState.contextUsage.cached_tokens,
1433
1477
  }
1434
1478
  : undefined, initialContent: restoreInputContent, onContextPercentageChange: setCurrentContextPercentage }),
1435
- vscodeState.vscodeConnectionStatus !== 'disconnected' && (React.createElement(Box, { marginTop: 1, paddingX: 1 },
1479
+ (vscodeState.vscodeConnectionStatus === 'connecting' ||
1480
+ vscodeState.vscodeConnectionStatus === 'connected') && (React.createElement(Box, { marginTop: 1, paddingX: 1 },
1436
1481
  React.createElement(Text, { color: vscodeState.vscodeConnectionStatus === 'connecting'
1437
1482
  ? 'yellow'
1438
- : vscodeState.vscodeConnectionStatus === 'connected'
1439
- ? 'green'
1440
- : vscodeState.vscodeConnectionStatus === 'error'
1441
- ? 'red'
1442
- : theme.colors.menuSecondary, dimColor: vscodeState.vscodeConnectionStatus !== 'error' },
1483
+ : 'green', dimColor: true }, vscodeState.vscodeConnectionStatus === 'connecting' ? (React.createElement(React.Fragment, null,
1484
+ React.createElement(Spinner, { type: "dots" }),
1485
+ " ",
1486
+ t.chatScreen.ideConnecting)) : (React.createElement(React.Fragment, null,
1443
1487
  "\u25CF",
1444
1488
  ' ',
1445
- vscodeState.vscodeConnectionStatus === 'connecting'
1446
- ? t.chatScreen.ideConnecting
1447
- : vscodeState.vscodeConnectionStatus === 'connected'
1448
- ? t.chatScreen.ideConnected
1449
- : vscodeState.vscodeConnectionStatus === 'error'
1450
- ? t.chatScreen.ideError
1451
- : 'IDE',
1452
- vscodeState.vscodeConnectionStatus === 'connected' &&
1453
- vscodeState.editorContext.activeFile &&
1489
+ t.chatScreen.ideConnected,
1490
+ vscodeState.editorContext.activeFile &&
1454
1491
  t.chatScreen.ideActiveFile.replace('{file}', vscodeState.editorContext.activeFile),
1455
- vscodeState.vscodeConnectionStatus === 'connected' &&
1456
- vscodeState.editorContext.selectedText &&
1457
- t.chatScreen.ideSelectedText.replace('{count}', vscodeState.editorContext.selectedText.length.toString())))),
1492
+ vscodeState.editorContext.selectedText &&
1493
+ t.chatScreen.ideSelectedText.replace('{count}', vscodeState.editorContext.selectedText.length.toString())))))),
1458
1494
  codebaseIndexing && codebaseProgress && (React.createElement(Box, { marginTop: 1, paddingX: 1 },
1459
1495
  React.createElement(Text, { color: "cyan", dimColor: true },
1460
1496
  React.createElement(Spinner, { type: "dots" }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-ai",
3
- "version": "0.4.14",
3
+ "version": "0.4.16",
4
4
  "description": "Intelligent Command Line Assistant powered by AI",
5
5
  "license": "MIT",
6
6
  "bin": {