snow-ai 0.3.36 → 0.4.0

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.
Files changed (97) hide show
  1. package/dist/agents/codebaseIndexAgent.js +1 -0
  2. package/dist/agents/codebaseReviewAgent.d.ts +61 -0
  3. package/dist/agents/codebaseReviewAgent.js +301 -0
  4. package/dist/agents/promptOptimizeAgent.d.ts +54 -0
  5. package/dist/agents/promptOptimizeAgent.js +268 -0
  6. package/dist/api/anthropic.js +1 -0
  7. package/dist/api/chat.js +1 -0
  8. package/dist/api/embedding.js +1 -0
  9. package/dist/api/gemini.js +2 -1
  10. package/dist/api/responses.js +1 -0
  11. package/dist/api/systemPrompt.d.ts +1 -5
  12. package/dist/api/systemPrompt.js +168 -100
  13. package/dist/app.js +14 -6
  14. package/dist/cli.js +1 -1
  15. package/dist/hooks/useCommandPanel.js +48 -46
  16. package/dist/hooks/useConversation.d.ts +2 -1
  17. package/dist/hooks/useConversation.js +116 -30
  18. package/dist/hooks/useGlobalExit.js +4 -2
  19. package/dist/hooks/useStreamingState.d.ts +9 -0
  20. package/dist/hooks/useStreamingState.js +3 -0
  21. package/dist/i18n/I18nContext.d.ts +14 -0
  22. package/dist/i18n/I18nContext.js +24 -0
  23. package/dist/i18n/index.d.ts +3 -0
  24. package/dist/i18n/index.js +2 -0
  25. package/dist/i18n/lang/en.d.ts +2 -0
  26. package/dist/i18n/lang/en.js +483 -0
  27. package/dist/i18n/lang/es.d.ts +2 -0
  28. package/dist/i18n/lang/es.js +483 -0
  29. package/dist/i18n/lang/ja.d.ts +2 -0
  30. package/dist/i18n/lang/ja.js +483 -0
  31. package/dist/i18n/lang/ko.d.ts +2 -0
  32. package/dist/i18n/lang/ko.js +483 -0
  33. package/dist/i18n/lang/zh-TW.d.ts +2 -0
  34. package/dist/i18n/lang/zh-TW.js +483 -0
  35. package/dist/i18n/lang/zh.d.ts +2 -0
  36. package/dist/i18n/lang/zh.js +483 -0
  37. package/dist/i18n/translations.d.ts +2 -0
  38. package/dist/i18n/translations.js +14 -0
  39. package/dist/i18n/types.d.ts +459 -0
  40. package/dist/i18n/types.js +1 -0
  41. package/dist/mcp/aceCodeSearch.d.ts +17 -48
  42. package/dist/mcp/aceCodeSearch.js +24 -56
  43. package/dist/mcp/bash.js +8 -1
  44. package/dist/mcp/codebaseSearch.d.ts +1 -1
  45. package/dist/mcp/codebaseSearch.js +159 -30
  46. package/dist/mcp/filesystem.d.ts +3 -80
  47. package/dist/mcp/filesystem.js +23 -103
  48. package/dist/mcp/subagent.d.ts +2 -1
  49. package/dist/mcp/subagent.js +54 -5
  50. package/dist/ui/components/ChatInput.js +22 -25
  51. package/dist/ui/components/CommandPanel.d.ts +1 -1
  52. package/dist/ui/components/CommandPanel.js +20 -13
  53. package/dist/ui/components/DiffViewer.d.ts +1 -1
  54. package/dist/ui/components/DiffViewer.js +101 -91
  55. package/dist/ui/components/FileList.js +22 -11
  56. package/dist/ui/components/HelpPanel.js +47 -21
  57. package/dist/ui/components/Menu.js +6 -2
  58. package/dist/ui/components/MessageList.d.ts +6 -0
  59. package/dist/ui/components/MessageList.js +1 -1
  60. package/dist/ui/components/ToolConfirmation.d.ts +4 -1
  61. package/dist/ui/components/ToolConfirmation.js +28 -2
  62. package/dist/ui/components/ToolResultPreview.d.ts +2 -1
  63. package/dist/ui/components/ToolResultPreview.js +41 -25
  64. package/dist/ui/pages/ChatScreen.js +177 -56
  65. package/dist/ui/pages/CodeBaseConfigScreen.js +54 -30
  66. package/dist/ui/pages/ConfigScreen.js +138 -98
  67. package/dist/ui/pages/CustomHeadersScreen.js +75 -69
  68. package/dist/ui/pages/LanguageSettingsScreen.d.ts +7 -0
  69. package/dist/ui/pages/LanguageSettingsScreen.js +89 -0
  70. package/dist/ui/pages/ProxyConfigScreen.js +27 -23
  71. package/dist/ui/pages/SensitiveCommandConfigScreen.js +32 -25
  72. package/dist/ui/pages/SubAgentConfigScreen.js +88 -75
  73. package/dist/ui/pages/SystemPromptConfigScreen.js +31 -26
  74. package/dist/ui/pages/WelcomeScreen.js +40 -26
  75. package/dist/utils/apiConfig.d.ts +2 -0
  76. package/dist/utils/codebaseConfig.d.ts +1 -5
  77. package/dist/utils/codebaseConfig.js +2 -10
  78. package/dist/utils/codebaseSearchEvents.d.ts +16 -0
  79. package/dist/utils/codebaseSearchEvents.js +13 -0
  80. package/dist/utils/commands/agent.js +2 -2
  81. package/dist/utils/commands/init.js +1 -1
  82. package/dist/utils/configManager.js +26 -5
  83. package/dist/utils/contextCompressor.js +1 -1
  84. package/dist/utils/languageConfig.d.ts +21 -0
  85. package/dist/utils/languageConfig.js +61 -0
  86. package/dist/utils/mcpToolsManager.js +0 -9
  87. package/dist/utils/notebookManager.js +11 -4
  88. package/dist/utils/sessionConverter.js +13 -3
  89. package/dist/utils/sessionManager.d.ts +1 -0
  90. package/dist/utils/subAgentConfig.d.ts +10 -5
  91. package/dist/utils/subAgentConfig.js +112 -19
  92. package/dist/utils/subAgentExecutor.d.ts +9 -1
  93. package/dist/utils/subAgentExecutor.js +122 -9
  94. package/dist/utils/toolExecutor.d.ts +2 -1
  95. package/dist/utils/toolExecutor.js +1 -2
  96. package/dist/utils/usageLogger.js +18 -3
  97. package/package.json +2 -1
@@ -37,7 +37,7 @@ export async function handleConversationWithTools(options) {
37
37
  const mcpTools = await collectAllMCPTools();
38
38
  // Build conversation history with TODO context as pinned user message
39
39
  let conversationMessages = [
40
- { role: 'system', content: getSystemPrompt(mcpTools) },
40
+ { role: 'system', content: getSystemPrompt() },
41
41
  ];
42
42
  // If there are TODOs, add pinned context message at the front
43
43
  if (existingTodoList && existingTodoList.todos.length > 0) {
@@ -430,32 +430,67 @@ export async function handleConversationWithTools(options) {
430
430
  const firstTool = sensitiveTools[0];
431
431
  const allTools = sensitiveTools.length > 1 ? sensitiveTools : undefined;
432
432
  const confirmation = await requestToolConfirmation(firstTool, undefined, allTools);
433
- if (confirmation === 'reject') {
433
+ if (confirmation === 'reject' ||
434
+ (typeof confirmation === 'object' &&
435
+ confirmation.type === 'reject_with_reply')) {
434
436
  setMessages(prev => prev.filter(msg => !msg.toolPending));
437
+ const rejectMessage = typeof confirmation === 'object'
438
+ ? `Tool execution rejected by user: ${confirmation.reason}`
439
+ : 'Error: Tool execution rejected by user';
440
+ // Create UI messages for rejected tools
441
+ const rejectedToolUIMessages = [];
435
442
  for (const toolCall of sensitiveTools) {
436
443
  const rejectionMessage = {
437
444
  role: 'tool',
438
445
  tool_call_id: toolCall.id,
439
- content: 'Error: Tool execution rejected by user',
446
+ content: rejectMessage,
440
447
  };
441
448
  conversationMessages.push(rejectionMessage);
442
449
  saveMessage(rejectionMessage).catch(error => {
443
450
  console.error('Failed to save tool rejection message:', error);
444
451
  });
445
- }
446
- setMessages(prev => [
447
- ...prev,
448
- {
452
+ // Add UI message for each rejected tool
453
+ const toolDisplay = formatToolCallMessage(toolCall);
454
+ const statusIcon = '✗';
455
+ let statusText = '';
456
+ if (typeof confirmation === 'object' && confirmation.reason) {
457
+ statusText = `\n └─ Rejection reason: ${confirmation.reason}`;
458
+ }
459
+ else {
460
+ statusText = `\n └─ ${rejectMessage}`;
461
+ }
462
+ rejectedToolUIMessages.push({
449
463
  role: 'assistant',
450
- content: 'Tool call rejected, session ended',
464
+ content: `${statusIcon} ${toolDisplay.toolName}${statusText}`,
451
465
  streaming: false,
452
- },
453
- ]);
454
- if (options.setIsStreaming) {
455
- options.setIsStreaming(false);
466
+ });
467
+ }
468
+ // Add rejected tool messages to UI
469
+ if (rejectedToolUIMessages.length > 0) {
470
+ setMessages(prev => [...prev, ...rejectedToolUIMessages]);
471
+ }
472
+ // If reject_with_reply, continue the conversation instead of ending
473
+ if (typeof confirmation === 'object' &&
474
+ confirmation.type === 'reject_with_reply') {
475
+ // Continue to next iteration - AI will see the rejection message and respond
476
+ continue;
477
+ }
478
+ else {
479
+ // Original reject behavior - end session
480
+ setMessages(prev => [
481
+ ...prev,
482
+ {
483
+ role: 'assistant',
484
+ content: 'Tool call rejected, session ended',
485
+ streaming: false,
486
+ },
487
+ ]);
488
+ if (options.setIsStreaming) {
489
+ options.setIsStreaming(false);
490
+ }
491
+ freeEncoder();
492
+ return { usage: accumulatedUsage };
456
493
  }
457
- freeEncoder();
458
- return { usage: accumulatedUsage };
459
494
  }
460
495
  // Approved, add sensitive tools to approved list
461
496
  approvedTools.push(...sensitiveTools);
@@ -467,32 +502,67 @@ export async function handleConversationWithTools(options) {
467
502
  ? toolsNeedingConfirmation
468
503
  : undefined;
469
504
  const confirmation = await requestToolConfirmation(firstTool, undefined, allTools);
470
- if (confirmation === 'reject') {
505
+ if (confirmation === 'reject' ||
506
+ (typeof confirmation === 'object' &&
507
+ confirmation.type === 'reject_with_reply')) {
471
508
  setMessages(prev => prev.filter(msg => !msg.toolPending));
509
+ const rejectMessage = typeof confirmation === 'object'
510
+ ? `Tool execution rejected by user: ${confirmation.reason}`
511
+ : 'Error: Tool execution rejected by user';
512
+ // Create UI messages for rejected tools
513
+ const rejectedToolUIMessages = [];
472
514
  for (const toolCall of toolsNeedingConfirmation) {
473
515
  const rejectionMessage = {
474
516
  role: 'tool',
475
517
  tool_call_id: toolCall.id,
476
- content: 'Error: Tool execution rejected by user',
518
+ content: rejectMessage,
477
519
  };
478
520
  conversationMessages.push(rejectionMessage);
479
521
  saveMessage(rejectionMessage).catch(error => {
480
522
  console.error('Failed to save tool rejection message:', error);
481
523
  });
482
- }
483
- setMessages(prev => [
484
- ...prev,
485
- {
524
+ // Add UI message for each rejected tool
525
+ const toolDisplay = formatToolCallMessage(toolCall);
526
+ const statusIcon = '✗';
527
+ let statusText = '';
528
+ if (typeof confirmation === 'object' && confirmation.reason) {
529
+ statusText = `\n └─ Rejection reason: ${confirmation.reason}`;
530
+ }
531
+ else {
532
+ statusText = `\n └─ ${rejectMessage}`;
533
+ }
534
+ rejectedToolUIMessages.push({
486
535
  role: 'assistant',
487
- content: 'Tool call rejected, session ended',
536
+ content: `${statusIcon} ${toolDisplay.toolName}${statusText}`,
488
537
  streaming: false,
489
- },
490
- ]);
491
- if (options.setIsStreaming) {
492
- options.setIsStreaming(false);
538
+ });
539
+ }
540
+ // Add rejected tool messages to UI
541
+ if (rejectedToolUIMessages.length > 0) {
542
+ setMessages(prev => [...prev, ...rejectedToolUIMessages]);
543
+ }
544
+ // If reject_with_reply, continue the conversation instead of ending
545
+ if (typeof confirmation === 'object' &&
546
+ confirmation.type === 'reject_with_reply') {
547
+ // Continue to next iteration - AI will see the rejection message and respond
548
+ continue;
549
+ }
550
+ else {
551
+ // Original reject behavior - end session
552
+ setMessages(prev => [
553
+ ...prev,
554
+ {
555
+ role: 'assistant',
556
+ content: 'Tool call rejected, session ended',
557
+ streaming: false,
558
+ },
559
+ ]);
560
+ if (options.setIsStreaming) {
561
+ options.setIsStreaming(false);
562
+ }
563
+ freeEncoder();
564
+ return { usage: accumulatedUsage };
493
565
  }
494
- freeEncoder();
495
- return { usage: accumulatedUsage };
496
566
  }
497
567
  // If approved_always, add ALL these tools to both global and session-approved sets
498
568
  if (confirmation === 'approve_always') {
@@ -535,7 +605,7 @@ export async function handleConversationWithTools(options) {
535
605
  name: toolCall.function.name,
536
606
  arguments: toolArgs,
537
607
  },
538
- toolDisplay,
608
+ // Don't include toolDisplay for sub-agent tools to avoid showing parameters
539
609
  toolCallId: toolCall.id,
540
610
  toolPending: true,
541
611
  subAgent: {
@@ -711,7 +781,9 @@ export async function handleConversationWithTools(options) {
711
781
  break;
712
782
  }
713
783
  // 在工具执行完成后、发送结果到AI前,检查是否需要压缩
714
- if (options.getCurrentContextPercentage &&
784
+ const config = getOpenAiConfig();
785
+ if (config.enableAutoCompress !== false &&
786
+ options.getCurrentContextPercentage &&
715
787
  shouldAutoCompress(options.getCurrentContextPercentage())) {
716
788
  try {
717
789
  // 显示压缩提示消息
@@ -763,12 +835,24 @@ export async function handleConversationWithTools(options) {
763
835
  const isError = result.content.startsWith('Error:');
764
836
  const statusIcon = isError ? '✗' : '✓';
765
837
  const statusText = isError ? `\n └─ ${result.content}` : '';
838
+ // Parse sub-agent result to extract usage information
839
+ let usage = undefined;
840
+ if (!isError) {
841
+ try {
842
+ const subAgentResult = JSON.parse(result.content);
843
+ usage = subAgentResult.usage;
844
+ }
845
+ catch (e) {
846
+ // Ignore parsing errors
847
+ }
848
+ }
766
849
  resultMessages.push({
767
850
  role: 'assistant',
768
851
  content: `${statusIcon} ${toolCall.function.name}${statusText}`,
769
852
  streaming: false,
770
853
  // Pass the full result.content for ToolResultPreview to parse
771
854
  toolResult: !isError ? result.content : undefined,
855
+ subAgentUsage: usage,
772
856
  });
773
857
  // Save the tool result to conversation history
774
858
  conversationMessages.push(result);
@@ -852,7 +936,9 @@ export async function handleConversationWithTools(options) {
852
936
  const pendingMessages = options.getPendingMessages();
853
937
  if (pendingMessages.length > 0) {
854
938
  // 检查 token 占用,如果 >= 80% 先执行自动压缩
855
- if (options.getCurrentContextPercentage &&
939
+ const config = getOpenAiConfig();
940
+ if (config.enableAutoCompress !== false &&
941
+ options.getCurrentContextPercentage &&
856
942
  shouldAutoCompress(options.getCurrentContextPercentage())) {
857
943
  try {
858
944
  // 显示压缩提示消息
@@ -1,6 +1,8 @@
1
1
  import { useInput } from 'ink';
2
2
  import { useState } from 'react';
3
+ import { useI18n } from '../i18n/index.js';
3
4
  export function useGlobalExit(onNotification) {
5
+ const { t } = useI18n();
4
6
  const [lastCtrlCTime, setLastCtrlCTime] = useState(0);
5
7
  const ctrlCTimeout = 1000; // 1 second timeout for double Ctrl+C
6
8
  useInput((input, key) => {
@@ -16,13 +18,13 @@ export function useGlobalExit(onNotification) {
16
18
  if (onNotification) {
17
19
  onNotification({
18
20
  show: true,
19
- message: 'Press Ctrl+C again to exit'
21
+ message: t.hooks.pressCtrlCAgain,
20
22
  });
21
23
  // Hide notification after timeout
22
24
  setTimeout(() => {
23
25
  onNotification({
24
26
  show: false,
25
- message: ''
27
+ message: '',
26
28
  });
27
29
  }, ctrlCTimeout);
28
30
  }
@@ -6,6 +6,13 @@ export type RetryStatus = {
6
6
  remainingSeconds?: number;
7
7
  errorMessage?: string;
8
8
  };
9
+ export type CodebaseSearchStatus = {
10
+ isSearching: boolean;
11
+ attempt: number;
12
+ maxAttempts: number;
13
+ currentTopN: number;
14
+ message: string;
15
+ };
9
16
  export declare function useStreamingState(): {
10
17
  isStreaming: boolean;
11
18
  setIsStreaming: import("react").Dispatch<import("react").SetStateAction<boolean>>;
@@ -21,4 +28,6 @@ export declare function useStreamingState(): {
21
28
  retryStatus: RetryStatus | null;
22
29
  setRetryStatus: import("react").Dispatch<import("react").SetStateAction<RetryStatus | null>>;
23
30
  animationFrame: number;
31
+ codebaseSearchStatus: CodebaseSearchStatus | null;
32
+ setCodebaseSearchStatus: import("react").Dispatch<import("react").SetStateAction<CodebaseSearchStatus | null>>;
24
33
  };
@@ -9,6 +9,7 @@ export function useStreamingState() {
9
9
  const [timerStartTime, setTimerStartTime] = useState(null);
10
10
  const [retryStatus, setRetryStatus] = useState(null);
11
11
  const [animationFrame, setAnimationFrame] = useState(0);
12
+ const [codebaseSearchStatus, setCodebaseSearchStatus] = useState(null);
12
13
  // Animation for streaming/saving indicator
13
14
  useEffect(() => {
14
15
  if (!isStreaming)
@@ -98,5 +99,7 @@ export function useStreamingState() {
98
99
  retryStatus,
99
100
  setRetryStatus,
100
101
  animationFrame,
102
+ codebaseSearchStatus,
103
+ setCodebaseSearchStatus,
101
104
  };
102
105
  }
@@ -0,0 +1,14 @@
1
+ import React, { ReactNode } from 'react';
2
+ import type { Language, TranslationKeys } from './types.js';
3
+ type I18nContextType = {
4
+ language: Language;
5
+ setLanguage: (lang: Language) => void;
6
+ t: TranslationKeys;
7
+ };
8
+ type Props = {
9
+ children: ReactNode;
10
+ defaultLanguage?: Language;
11
+ };
12
+ export declare function I18nProvider({ children, defaultLanguage }: Props): React.JSX.Element;
13
+ export declare function useI18n(): I18nContextType;
14
+ export {};
@@ -0,0 +1,24 @@
1
+ import React, { createContext, useState, useCallback } from 'react';
2
+ import { translations } from './translations.js';
3
+ import { getCurrentLanguage, setCurrentLanguage, } from '../utils/languageConfig.js';
4
+ const I18nContext = createContext(undefined);
5
+ export function I18nProvider({ children, defaultLanguage }) {
6
+ // Load saved language on mount or use default
7
+ const [language, setLanguageState] = useState(() => {
8
+ return defaultLanguage || getCurrentLanguage();
9
+ });
10
+ const setLanguage = useCallback((lang) => {
11
+ setLanguageState(lang);
12
+ setCurrentLanguage(lang); // Persist to file system
13
+ }, []);
14
+ // Get translations for current language
15
+ const t = translations[language];
16
+ return (React.createElement(I18nContext.Provider, { value: { language, setLanguage, t } }, children));
17
+ }
18
+ export function useI18n() {
19
+ const context = React.useContext(I18nContext);
20
+ if (!context) {
21
+ throw new Error('useI18n must be used within I18nProvider');
22
+ }
23
+ return context;
24
+ }
@@ -0,0 +1,3 @@
1
+ export { I18nProvider, useI18n } from './I18nContext.js';
2
+ export type { Language, TranslationKeys, Translations } from './types.js';
3
+ export { translations } from './translations.js';
@@ -0,0 +1,2 @@
1
+ export { I18nProvider, useI18n } from './I18nContext.js';
2
+ export { translations } from './translations.js';
@@ -0,0 +1,2 @@
1
+ import type { TranslationKeys } from '../types.js';
2
+ export declare const en: TranslationKeys;