Repository: opensumi/core
Pull request #4407: chore: improve chat context
Author: login: Aaaaash
Created: 2025-02-25T01:49:48Z | Merged: 2025-03-04T12:23:06Z

### Types

<!-- Please delete this line and the unselected items below to keep the PR description clean -->

- [ ] 🎉 New Features
- [ ] 🐛 Bug Fixes
- [ ] 📚 Documentation Changes
- [ ] 💄 Code Style Changes
- [ ] 💄 Style Changes
- [ ] 🪚 Refactors
- [ ] 🚀 Performance Improvements
- [ ] 🏗️ Build System
- [ ] ⏱ Tests
- [ ] 🧹 Chores
- [ ] Other Changes

### Background or solution

### Changelog


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **新功能**
  - 文件管理现支持分别显示附件文件与近期浏览文件，提升了文件信息展示的准确性。

- **重构**
  - 优化了文件添加及更新逻辑，使文件列表处理更精准、高效。

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/packages/ai-native/__test__/browser/chat/chat-agent.service.test.ts b/packages/ai-native/__test__/browser/chat/chat-agent.service.test.ts
index 2cb3c4db9f..e7931f2aac 100644
--- a/packages/ai-native/__test__/browser/chat/chat-agent.service.test.ts
+++ b/packages/ai-native/__test__/browser/chat/chat-agent.service.test.ts
@@ -1,9 +1,12 @@
-import { CancellationToken } from '@opensumi/ide-core-common';
+import { CancellationToken, Emitter } from '@opensumi/ide-core-common';
+import { ChatFeatureRegistryToken, ChatServiceToken } from '@opensumi/ide-core-common/lib/types/ai-native';
 import { createBrowserInjector } from '@opensumi/ide-dev-tool/src/injector-helper';
 import { MockInjector } from '@opensumi/ide-dev-tool/src/mock-injector';
 
 import { ChatAgentService } from '../../../lib/browser/chat/chat-agent.service';
 import { IChatAgent, IChatAgentMetadata, IChatAgentRequest, IChatManagerService } from '../../../lib/common';
+import { LLMContextServiceToken } from '../../../lib/common/llm-context';
+import { ChatAgentPromptProvider } from '../../../lib/common/prompts/context-prompt-provider';
 
 describe('ChatAgentService', () => {
   let injector: MockInjector;
@@ -19,9 +22,32 @@ describe('ChatAgentService', () => {
             startSession: jest.fn(),
           },
         },
+        {
+          token: ChatAgentPromptProvider,
+          useValue: {
+            provideContextPrompt: (val, msg) => msg,
+          },
+        },
+        {
+          token: ChatServiceToken,
+          useValue: {
+          },
+        },
+        {
+          token: LLMContextServiceToken,
+          useValue: {
+            onDidContextFilesChangeEvent: new Emitter().event,
+            serialize: () => { },
+          },
+        },
+        {
+          token: ChatFeatureRegistryToken,
+          useValue: {
+          },
+        },
       ]),
     );
-    chatAgentService = new ChatAgentService();
+    chatAgentService = injector.get(ChatAgentService);
   });
 
   it('should register an agent', () => {
@@ -42,7 +68,7 @@ describe('ChatAgentService', () => {
       id: 'agent1',
       metadata: {},
       provideSlashCommands: () => Promise.resolve([]),
-      invoke: () => {},
+      invoke: () => { },
     } as unknown as IChatAgent;
     chatAgentService.registerAgent(agent);
 
diff --git a/packages/ai-native/src/browser/chat/chat-agent.service.ts b/packages/ai-native/src/browser/chat/chat-agent.service.ts
index 052a2abba4..1faa513b20 100644
--- a/packages/ai-native/src/browser/chat/chat-agent.service.ts
+++ b/packages/ai-native/src/browser/chat/chat-agent.service.ts
@@ -4,6 +4,7 @@ import { Autowired, Injectable } from '@opensumi/di';
 import {
   CancellationToken,
   ChatFeatureRegistryToken,
+  ChatMessageRole,
   ChatServiceToken,
   Disposable,
   Emitter,
@@ -24,6 +25,8 @@ import {
   IChatFollowup,
   IChatMessageStructure,
 } from '../../common';
+import { LLMContextService, LLMContextServiceToken } from '../../common/llm-context';
+import { ChatAgentPromptProvider } from '../../common/prompts/context-prompt-provider';
 import { IChatFeatureRegistry } from '../types';
 
 import { ChatService } from './chat.api.service';
@@ -34,6 +37,12 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
 
   private defaultAgentId: string | undefined;
 
+  private initialUserMessageMap: Map<string, string> = new Map();
+
+  private shouldUpdateContext = false;
+
+  private contextVersion: number;
+
   private readonly _onDidChangeAgents = new Emitter<void>();
   readonly onDidChangeAgents = this._onDidChangeAgents.event;
 
@@ -43,6 +52,12 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
   @Autowired(ILogger)
   logger: ILogger;
 
+  @Autowired(LLMContextServiceToken)
+  protected readonly contextService: LLMContextService;
+
+  @Autowired(ChatAgentPromptProvider)
+  protected readonly promptProvider: ChatAgentPromptProvider;
+
   @Autowired(ChatServiceToken)
   private aiChatService: ChatService;
 
@@ -52,6 +67,12 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
   constructor() {
     super();
     this.addDispose(this._onDidChangeAgents);
+    this.addDispose(this.contextService.onDidContextFilesChangeEvent((event) => {
+      if (event.version !== this.contextVersion) {
+        this.contextVersion = event.version;
+        this.shouldUpdateContext = true;
+      }
+    }));
   }
 
   registerAgent(agent: IChatAgent): IDisposable {
@@ -119,10 +140,28 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
       throw new Error(`No agent with id ${id}`);
     }
 
+    // 发送第一条消息时携带初始 context
+    if (!this.initialUserMessageMap.has(request.sessionId)) {
+      this.initialUserMessageMap.set(request.sessionId, request.message);
+      const rawMessage = request.message;
+      request.message = this.provideContextMessage(rawMessage);
+    }
+
+    if (this.shouldUpdateContext) {
+      request.message = this.provideContextMessage(request.message);
+      this.shouldUpdateContext = false;
+    }
+
     const result = await data.agent.invoke(request, progress, history, token);
     return result;
   }
 
+  private provideContextMessage(message: string) {
+    const context = this.contextService.serialize();
+    const fullMessage = this.promptProvider.provideContextPrompt(context, message);
+    return fullMessage;
+  }
+
   populateChatInput(id: string, message: IChatMessageStructure) {
     this.aiChatService.sendMessage({
       ...message,
diff --git a/packages/ai-native/src/browser/chat/chat-model.ts b/packages/ai-native/src/browser/chat/chat-model.ts
index b8f661ae96..45d33f303d 100644
--- a/packages/ai-native/src/browser/chat/chat-model.ts
+++ b/packages/ai-native/src/browser/chat/chat-model.ts
@@ -1,4 +1,4 @@
-import { Autowired, INJECTOR_TOKEN, Injectable, Injector } from '@opensumi/di';
+import { Injectable } from '@opensumi/di';
 import {
   Disposable,
   Emitter,
@@ -8,8 +8,6 @@ import {
   IChatProgress,
   IChatToolContent,
   IChatTreeData,
-  ILogger,
-  memoize,
   uuid,
 } from '@opensumi/ide-core-common';
 import { MarkdownString, isMarkdownString } from '@opensumi/monaco-editor-core/esm/vs/base/common/htmlContent';
@@ -274,13 +272,18 @@ export class ChatRequestModel implements IChatRequestModel {
 export class ChatModel extends Disposable implements IChatModel {
   private static requestIdPool = 0;
 
-  constructor(initParams?: { sessionId?: string; history?: MsgHistoryManager; requests?: ChatRequestModel[] }) {
+  constructor(initParams?: {
+    sessionId?: string;
+    history?: MsgHistoryManager;
+    requests?: ChatRequestModel[];
+  }) {
     super();
     this.#sessionId = initParams?.sessionId ?? uuid();
     this.history = initParams?.history ?? new MsgHistoryManager();
     if (initParams?.requests) {
       this.#requests = new Map(initParams.requests.map((r) => [r.requestId, r]));
     }
+
   }
 
   #sessionId: string;
@@ -300,9 +303,11 @@ export class ChatModel extends Disposable implements IChatModel {
   readonly history: MsgHistoryManager;
 
   addRequest(message: IChatRequestMessage): ChatRequestModel {
+    const msg = message;
+
     const requestId = `${this.sessionId}_request_${ChatModel.requestIdPool++}`;
-    const response = new ChatResponseModel(requestId, this, message.agentId);
-    const request = new ChatRequestModel(requestId, this, message, response);
+    const response = new ChatResponseModel(requestId, this, msg.agentId);
+    const request = new ChatRequestModel(requestId, this, msg, response);
 
     this.#requests.set(requestId, request);
     return request;
diff --git a/packages/ai-native/src/browser/chat/chat-proxy.service.ts b/packages/ai-native/src/browser/chat/chat-proxy.service.ts
index b7d93c727b..fa0f67e085 100644
--- a/packages/ai-native/src/browser/chat/chat-proxy.service.ts
+++ b/packages/ai-native/src/browser/chat/chat-proxy.service.ts
@@ -12,7 +12,7 @@ import {
   IAIReporter,
   IApplicationService,
   IChatProgress,
-  uuid,
+  getOperatingSystemName,
 } from '@opensumi/ide-core-common';
 import { AINativeSettingSectionsId } from '@opensumi/ide-core-common/lib/settings/ai-native';
 import { IChatMessage } from '@opensumi/ide-core-common/lib/types/ai-native';
@@ -92,7 +92,7 @@ export class ChatProxyService extends Disposable {
               AINativeSettingSectionsId.SystemPrompt,
               'You are a powerful AI coding assistant working in OpenSumi, a top IDE framework. You collaborate with a USER to solve coding tasks, which may involve creating, modifying, or debugging code, or answering questions. When the USER sends a message, relevant context (e.g., open files, cursor position, edit history, linter errors) may be attached. Use this information as needed.\n\n<tool_calling>\nYou have access to tools to assist with tasks. Follow these rules:\n1. Always adhere to the tool call schema and provide all required parameters.\n2. Only use tools explicitly provided; ignore unavailable ones.\n3. Avoid mentioning tool names to the USER (e.g., say "I will edit your file" instead of "I need to use the edit_file tool").\n4. Only call tools when necessary; respond directly if the task is general or you already know the answer.\n5. Explain to the USER why you’re using a tool before calling it.\n</tool_calling>\n\n<making_code_changes>\nWhen modifying code:\n1. Use code edit tools instead of outputting code unless explicitly requested.\n2. Limit tool calls to one per turn.\n3. Ensure generated code is immediately executable by including necessary imports, dependencies, and endpoints.\n4. For new projects, create a dependency management file (e.g., requirements.txt) and a README.\n5. For web apps, design a modern, user-friendly UI.\n6. Avoid generating non-textual or excessively long code.\n7. Read file contents before editing, unless appending a small change or creating a new file.\n8. Fix introduced linter errors if possible, but stop after 3 attempts and ask the USER for guidance.\n9. Reapply reasonable code edits if they weren’t followed initially.\n</making_code_changes>\n\nUse the appropriate tools to fulfill the USER’s request, ensuring all required parameters are provided or inferred from context.',
             ) +
-            `\n\n<user_info>\nThe user's OS version is ${this.applicationService.frontendOS}. The absolute path of the user's workspace is ${this.appConfig.workspaceDir}.\n</user_info>`,
+            `\n\n<user_info>\nThe user's OS is ${getOperatingSystemName()}. The absolute path of the user's workspace is ${this.appConfig.workspaceDir}.\n</user_info>`,
         },
         invoke: async (
           request: IChatAgentRequest,
diff --git a/packages/ai-native/src/browser/chat/chat.module.less b/packages/ai-native/src/browser/chat/chat.module.less
index 23cec7f859..8188ba348c 100644
--- a/packages/ai-native/src/browser/chat/chat.module.less
+++ b/packages/ai-native/src/browser/chat/chat.module.less
@@ -279,7 +279,6 @@
   }
 }
 
-
 .chat_tips_container {
   display: flex;
   align-items: center;
@@ -290,6 +289,6 @@
 }
 
 .chat_history {
-  width: calc(100% - 60px);
+  width: calc(100% - 40px);
   color: var(--design-text-foreground);
 }
diff --git a/packages/ai-native/src/browser/chat/chat.view.tsx b/packages/ai-native/src/browser/chat/chat.view.tsx
index 8f2fd19963..ce8e20923d 100644
--- a/packages/ai-native/src/browser/chat/chat.view.tsx
+++ b/packages/ai-native/src/browser/chat/chat.view.tsx
@@ -5,7 +5,6 @@ import {
   AINativeConfigService,
   CommandService,
   getIcon,
-  useEventEffect,
   useInjectable,
   useUpdateOnEvent,
 } from '@opensumi/ide-core-browser';
@@ -42,8 +41,6 @@ import {
   IChatMessageStructure,
   TokenMCPServerProxyService,
 } from '../../common';
-import { LLMContextService, LLMContextServiceToken } from '../../common/llm-context';
-import { ChatAgentPromptProvider } from '../../common/prompts/context-prompt-provider';
 import { ChatContext } from '../components/ChatContext';
 import { CodeBlockWrapperInput } from '../components/ChatEditor';
 import ChatHistory, { IChatHistoryItem } from '../components/ChatHistory';
@@ -82,18 +79,12 @@ export const AIChatView = () => {
   const chatAgentService = useInjectable<IChatAgentService>(IChatAgentService);
   const chatFeatureRegistry = useInjectable<ChatFeatureRegistry>(ChatFeatureRegistryToken);
   const chatRenderRegistry = useInjectable<ChatRenderRegistry>(ChatRenderRegistryToken);
-  const contextService = useInjectable<LLMContextService>(LLMContextServiceToken);
-  const promptProvider = useInjectable<ChatAgentPromptProvider>(ChatAgentPromptProvider);
-  const mcpServerProxyService = useInjectable<MCPServerProxyService>(TokenMCPServerProxyService);
 
   const layoutService = useInjectable<IMainLayoutService>(IMainLayoutService);
   const msgHistoryManager = aiChatService.sessionModel.history;
   const containerRef = React.useRef<HTMLDivElement>(null);
   const autoScroll = React.useRef<boolean>(true);
   const chatInputRef = React.useRef<{ setInputValue: (v: string) => void } | null>(null);
-  const dialogService = useInjectable<IDialogService>(IDialogService);
-  const aiNativeConfigService = useInjectable<AINativeConfigService>(AINativeConfigService);
-  const commandService = useInjectable<CommandService>(CommandService);
 
   const [shortcutCommands, setShortcutCommands] = React.useState<ChatSlashCommandItemModel[]>([]);
 
@@ -115,8 +106,6 @@ export const AIChatView = () => {
   const [defaultAgentId, setDefaultAgentId] = React.useState<string>('');
   const [command, setCommand] = React.useState('');
   const [theme, setTheme] = React.useState<string | null>(null);
-  const [mcpToolsCount, setMcpToolsCount] = React.useState<number>(0);
-  const [mcpServersCount, setMcpServersCount] = React.useState<number>(0);
 
   React.useEffect(() => {
     const featureSlashCommands = chatFeatureRegistry.getAllShortcutSlashCommand();
@@ -537,10 +526,7 @@ export const AIChatView = () => {
       const { message, agentId, command, reportExtra } = value;
       const { actionType, actionSource } = reportExtra || {};
 
-      const context = contextService.serialize();
-      const fullMessage = await promptProvider.provideContextPrompt(context, message);
-
-      const request = aiChatService.createRequest(fullMessage, agentId!, command);
+      const request = aiChatService.createRequest(message, agentId!, command);
       if (!request) {
         return;
       }
@@ -689,32 +675,6 @@ export const AIChatView = () => {
     };
   }, [aiChatService.sessionModel]);
 
-  useEventEffect(
-    mcpServerProxyService.onChangeMCPServers,
-    () => {
-      mcpServerProxyService.getAllMCPTools().then((tools) => {
-        setMcpToolsCount(tools.length);
-      });
-      mcpServerProxyService.$getServers().then((servers) => {
-        setMcpServersCount(servers.length);
-      });
-    },
-    [mcpServerProxyService],
-  );
-
-  const handleShowMCPTools = React.useCallback(async () => {
-    const tools = await mcpServerProxyService.getAllMCPTools();
-    dialogService.open({
-      message: <MCPToolsDialog tools={tools} />,
-      type: MessageType.Empty,
-      buttons: ['关闭'],
-    });
-  }, [mcpServerProxyService, dialogService]);
-
-  const handleShowMCPConfig = React.useCallback(() => {
-    commandService.executeCommand(OPEN_MCP_CONFIG_COMMAND.id);
-  }, [commandService]);
-
   return (
     <div id={styles.ai_chat_view}>
       <div className={styles.header_container}>
@@ -754,18 +714,6 @@ export const AIChatView = () => {
                   </Popover>
                 ))}
               </div>
-              <div className={styles.header_operate_right}>
-                {aiNativeConfigService.capabilities.supportsMCP && (
-                  <>
-                    <div className={styles.tag} onClick={handleShowMCPConfig}>
-                      {`MCP Servers: ${mcpServersCount}`}
-                    </div>
-                    <div className={styles.tag} onClick={handleShowMCPTools}>
-                      {`MCP Tools: ${mcpToolsCount}`}
-                    </div>
-                  </>
-                )}
-              </div>
             </div>
             <ChatInputWrapperRender
               onSend={(value, agentId, command) =>
@@ -808,6 +756,8 @@ export function DefaultChatViewHeader({
   const aiNativeConfigService = useInjectable<AINativeConfigService>(AINativeConfigService);
   const mcpServerProxyService = useInjectable<MCPServerProxyService>(TokenMCPServerProxyService);
   const aiChatService = useInjectable<ChatInternalService>(IChatInternalService);
+  const commandService = useInjectable<CommandService>(CommandService);
+
   const [historyList, setHistoryList] = React.useState<IChatHistoryItem[]>([]);
   const [currentTitle, setCurrentTitle] = React.useState<string>('');
   const handleNewChat = React.useCallback(() => {
@@ -828,6 +778,10 @@ export function DefaultChatViewHeader({
     [aiChatService],
   );
 
+  const handleShowMCPConfig = React.useCallback(() => {
+    commandService.executeCommand(OPEN_MCP_CONFIG_COMMAND.id);
+  }, [commandService]);
+
   const handleShowMCPTools = React.useCallback(async () => {
     const tools = await mcpServerProxyService.getAllMCPTools();
     dialogService.open({
@@ -916,23 +870,6 @@ export function DefaultChatViewHeader({
           ariaLabel={localize('aiNative.operate.clear.title')}
         />
       </Popover>
-      {aiNativeConfigService.capabilities.supportsMCP && (
-        <Popover
-          overlayClassName={styles.popover_icon}
-          id={'ai-chat-header-tools'}
-          position={PopoverPosition.left}
-          title={localize('aiNative.operate.tools.title')}
-        >
-          <EnhanceIcon
-            wrapperClassName={styles.action_btn}
-            className={getIcon('menubar-tool')}
-            onClick={handleShowMCPTools}
-            tabIndex={0}
-            role='button'
-            ariaLabel={localize('aiNative.operate.tools.title')}
-          />
-        </Popover>
-      )}
       <Popover
         overlayClassName={styles.popover_icon}
         id={'ai-chat-header-close'}
diff --git a/packages/ai-native/src/browser/components/ChatContext/index.tsx b/packages/ai-native/src/browser/components/ChatContext/index.tsx
index c89617aafb..d38aa07031 100644
--- a/packages/ai-native/src/browser/components/ChatContext/index.tsx
+++ b/packages/ai-native/src/browser/components/ChatContext/index.tsx
@@ -39,7 +39,7 @@ export const ChatContext = memo(() => {
       50,
     )((files) => {
       if (files) {
-        updateAddedFiles(files);
+        updateAddedFiles([...files.attached]);
       }
     }, contextService);
 
@@ -57,7 +57,7 @@ export const ChatContext = memo(() => {
   }, []);
 
   const onDidDeselect = useCallback((uri: URI) => {
-    contextService.removeFileFromContext(uri);
+    contextService.removeFileFromContext(uri, true);
   }, []);
 
   const onDidClickFile = useCallback((uri: URI) => {
diff --git a/packages/ai-native/src/browser/components/ChatInput.tsx b/packages/ai-native/src/browser/components/ChatInput.tsx
index 1f73190449..3710e05496 100644
--- a/packages/ai-native/src/browser/components/ChatInput.tsx
+++ b/packages/ai-native/src/browser/components/ChatInput.tsx
@@ -1,18 +1,29 @@
 import cls from 'classnames';
 import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
 
-import { useInjectable, useLatest } from '@opensumi/ide-core-browser';
+import { AINativeConfigService, useInjectable, useLatest } from '@opensumi/ide-core-browser';
 import { Icon, Popover, PopoverPosition, getIcon } from '@opensumi/ide-core-browser/lib/components';
 import { EnhanceIcon } from '@opensumi/ide-core-browser/lib/components/ai-native';
 import { InteractiveInput } from '@opensumi/ide-core-browser/lib/components/ai-native/interactive-input/index';
-import { ChatAgentViewServiceToken, ChatFeatureRegistryToken, localize, runWhenIdle } from '@opensumi/ide-core-common';
+import {
+  ChatAgentViewServiceToken,
+  ChatFeatureRegistryToken,
+  MessageType,
+  localize,
+  runWhenIdle,
+} from '@opensumi/ide-core-common';
+import { CommandService } from '@opensumi/ide-core-common/lib/command';
 import { MonacoCommandRegistry } from '@opensumi/ide-editor/lib/browser/monaco-contrib/command/command.service';
+import { IDialogService } from '@opensumi/ide-overlay';
 
-import { AT_SIGN_SYMBOL, IChatAgentService, SLASH_SYMBOL } from '../../common';
+import { AT_SIGN_SYMBOL, IChatAgentService, SLASH_SYMBOL, TokenMCPServerProxyService } from '../../common';
 import { ChatAgentViewService } from '../chat/chat-agent.view.service';
 import { ChatSlashCommandItemModel } from '../chat/chat-model';
 import { ChatProxyService } from '../chat/chat-proxy.service';
 import { ChatFeatureRegistry } from '../chat/chat.feature.registry';
+import { OPEN_MCP_CONFIG_COMMAND } from '../mcp/config/mcp-config.commands';
+import { MCPServerProxyService } from '../mcp/mcp-server-proxy.service';
+import { MCPToolsDialog } from '../mcp/mcp-tools-dialog.view';
 import { IChatSlashCommandItem } from '../types';
 
 import styles from './components.module.less';
@@ -194,12 +205,29 @@ export const ChatInput = React.forwardRef((props: IChatInputProps, ref) => {
   const [isExpand, setIsExpand] = useState(false);
   const [placeholder, setPlaceHolder] = useState(localize('aiNative.chat.input.placeholder.default'));
 
+  const dialogService = useInjectable<IDialogService>(IDialogService);
+  const aiNativeConfigService = useInjectable<AINativeConfigService>(AINativeConfigService);
+  const mcpServerProxyService = useInjectable<MCPServerProxyService>(TokenMCPServerProxyService);
   const monacoCommandRegistry = useInjectable<MonacoCommandRegistry>(MonacoCommandRegistry);
   const chatAgentService = useInjectable<IChatAgentService>(IChatAgentService);
   const chatFeatureRegistry = useInjectable<ChatFeatureRegistry>(ChatFeatureRegistryToken);
+  const commandService = useInjectable<CommandService>(CommandService);
 
   const currentAgentIdRef = useLatest(agentId);
 
+  const handleShowMCPConfig = React.useCallback(() => {
+    commandService.executeCommand(OPEN_MCP_CONFIG_COMMAND.id);
+  }, [commandService]);
+
+  const handleShowMCPTools = React.useCallback(async () => {
+    const tools = await mcpServerProxyService.getAllMCPTools();
+    dialogService.open({
+      message: <MCPToolsDialog tools={tools} />,
+      type: MessageType.Empty,
+      buttons: [localize('dialog.file.close')],
+    });
+  }, [mcpServerProxyService, dialogService]);
+
   useImperativeHandle(ref, () => ({
     setInputValue: (v: string) => {
       setValue(v);
@@ -461,6 +489,42 @@ export const ChatInput = React.forwardRef((props: IChatInputProps, ref) => {
         height={inputHeight}
         popoverPosition={PopoverPosition.left}
       />
+      <div className={styles.chat_input_footer}>
+        {aiNativeConfigService.capabilities.supportsMCP && (
+          <div className={styles.mcp_desc}>
+            <Popover
+              overlayClassName={styles.popover_icon}
+              id={'ai-chat-header-mcp-server'}
+              position={PopoverPosition.left}
+              title={'MCP Server'}
+            >
+              <EnhanceIcon
+                wrapperClassName={styles.action_btn}
+                className={'codicon codicon-server'}
+                onClick={handleShowMCPConfig}
+                tabIndex={0}
+                role='button'
+                ariaLabel={'MCP Server'}
+              />
+            </Popover>
+            <Popover
+              overlayClassName={styles.popover_icon}
+              id={'ai-chat-header-tools'}
+              position={PopoverPosition.left}
+              title={localize('aiNative.operate.tools.title')}
+            >
+              <EnhanceIcon
+                wrapperClassName={styles.action_btn}
+                className={getIcon('menubar-tool')}
+                onClick={handleShowMCPTools}
+                tabIndex={0}
+                role='button'
+                ariaLabel={localize('aiNative.operate.tools.title')}
+              />
+            </Popover>
+          </div>
+        )}
+      </div>
     </div>
   );
 });
diff --git a/packages/ai-native/src/browser/components/ChatToolRender.tsx b/packages/ai-native/src/browser/components/ChatToolRender.tsx
index b8231de13d..b8004da8c7 100644
--- a/packages/ai-native/src/browser/components/ChatToolRender.tsx
+++ b/packages/ai-native/src/browser/components/ChatToolRender.tsx
@@ -67,13 +67,12 @@ export const ChatToolRender = (props: { value: IChatToolContent['content']; mess
     <div className={styles['chat-tool-render']}>
       <div className={styles['tool-header']} onClick={toggleExpand}>
         <div className={styles['tool-name']}>
-          <span className={cls(styles['expand-icon'], { [styles.expanded]: isExpanded })}>▶</span>
+          <Icon iconClass={`codicon codicon-triangle-${isExpanded ? 'down' : 'right'}`} />
           {label}
         </div>
         {value.state && (
           <div className={styles['tool-state']}>
             <span className={styles['state-icon']}>{stateInfo.icon}</span>
-            <span className={styles['state-label']}>{stateInfo.label}</span>
           </div>
         )}
       </div>
diff --git a/packages/ai-native/src/browser/components/chat-history.module.less b/packages/ai-native/src/browser/components/chat-history.module.less
index 94a8e76a32..f1bbffcddc 100644
--- a/packages/ai-native/src/browser/components/chat-history.module.less
+++ b/packages/ai-native/src/browser/components/chat-history.module.less
@@ -3,7 +3,7 @@
   align-items: center;
   justify-content: space-between;
   font-size: 13px;
-  padding: 0 4px 0 12px;
+  padding: 0 0 0 12px;
   color: var(--editor-foreground);
   text-overflow: ellipsis;
   white-space: nowrap;
diff --git a/packages/ai-native/src/browser/components/components.module.less b/packages/ai-native/src/browser/components/components.module.less
index a5cf0abfc0..25ee1a48e0 100644
--- a/packages/ai-native/src/browser/components/components.module.less
+++ b/packages/ai-native/src/browser/components/components.module.less
@@ -96,6 +96,7 @@
 .chat_input_container {
   position: relative;
   border-radius: 9px;
+  padding: 10px 0px 0px 0px;
   border: 1px solid var(--kt-input-border);
   background-color: var(--design-chatInput-background);
   &.active {
@@ -505,3 +506,22 @@
     }
   }
 }
+
+.chat_input_footer {
+  padding: 0px 10px;
+  display: flex;
+  align-items: center;
+  font-size: 12px;
+
+  .model_selector {
+    display: flex;
+    font-size: 11px;
+    align-items: center;
+  }
+
+  .mcp_desc {
+    padding: 5px 0px;
+    display: flex;
+    align-items: center;
+  }
+}
diff --git a/packages/ai-native/src/browser/context/llm-context.service.ts b/packages/ai-native/src/browser/context/llm-context.service.ts
index d812ab7dd9..cf51c14879 100644
--- a/packages/ai-native/src/browser/context/llm-context.service.ts
+++ b/packages/ai-native/src/browser/context/llm-context.service.ts
@@ -12,7 +12,7 @@ import {
 import { EditorSelectionChangeEvent } from '@opensumi/ide-editor/lib/browser/types';
 import { IMarkerService } from '@opensumi/ide-markers/lib/common/types';
 
-import { FileContext, LLMContextService, SerializedContext } from '../../common/llm-context';
+import { AttachFileContext, FileContext, LLMContextService, SerializedContext } from '../../common/llm-context';
 
 @Injectable()
 export class LLMContextServiceImpl extends WithEventBus implements LLMContextService {
@@ -27,40 +27,68 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
 
   private isAutoCollecting = false;
 
-  private contextFiles: FileContext[] = [];
+  private contextVersion = 0;
 
-  private maxFiles: number = 10; // 上下文的最大长度限制
-
-  private onDidContextFilesChangeEmitter = new Emitter<FileContext[]>();
+  private readonly maxAttachFilesLimit = 10;
+  private readonly maxViewFilesLimit = 20;
+  private readonly attachedFiles: FileContext[] = [];
+  private readonly recentlyViewFiles: FileContext[] = [];
+  private readonly onDidContextFilesChangeEmitter = new Emitter<{ viewed: FileContext[]; attached: FileContext[]; version: number }>();
   onDidContextFilesChangeEvent = this.onDidContextFilesChangeEmitter.event;
 
-  addFileToContext(uri: URI, selection?: [number, number], isManual = true): void {
-    this.removeFileFromContext(uri);
+  private addFileToList(file: FileContext, list: FileContext[], maxLimit: number) {
+    const existingIndex = list.findIndex((f) => f.uri.toString() === file.uri.toString());
+    if (existingIndex > -1) {
+      list.splice(existingIndex, 1);
+    }
+
+    list.push(file);
+    if (list.length > maxLimit) {
+      list.shift();
+    }
+  }
+
+  addFileToContext(uri: URI, selection?: [number, number], isManual = false): void {
+    if (!uri) {
+      return;
+    }
 
-    this.contextFiles.push({ uri, selection, isManual });
+    const file = { uri, selection };
+    const targetList = isManual ? this.attachedFiles : this.recentlyViewFiles;
+    const maxLimit = isManual ? this.maxAttachFilesLimit : this.maxViewFilesLimit;
 
-    if (this.contextFiles.length > this.maxFiles) {
-      this.contextFiles.shift();
+    if (isManual) {
+      this.docModelManager.createModelReference(uri);
     }
 
+    this.addFileToList(file, targetList, maxLimit);
+    this.notifyContextChange();
+  }
+
+  private notifyContextChange(): void {
     this.onDidContextFilesChangeEmitter.fire(this.getAllContextFiles());
   }
 
   cleanFileContext() {
-    this.contextFiles = [];
-    this.onDidContextFilesChangeEmitter.fire(this.getAllContextFiles());
+    this.attachedFiles.length = 0;
+    this.notifyContextChange();
   }
 
   private getAllContextFiles() {
-    return [...this.contextFiles];
+    return {
+      viewed: this.recentlyViewFiles,
+      attached: this.attachedFiles,
+      version: this.contextVersion++,
+    };
   }
 
-  removeFileFromContext(uri: URI): void {
-    const index = this.contextFiles.findIndex((file) => file.uri.toString() === uri.toString());
+  removeFileFromContext(uri: URI, isManual = false): void {
+    const targetList = isManual ? this.attachedFiles : this.recentlyViewFiles;
+    const index = targetList.findIndex((file) => file.uri.toString() === uri.toString());
     if (index > -1) {
-      this.contextFiles.splice(index, 1);
-      this.onDidContextFilesChangeEmitter.fire(this.getAllContextFiles());
+      targetList.splice(index, 1);
     }
+    this.notifyContextChange();
   }
 
   startAutoCollection(): void {
@@ -78,8 +106,7 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
         if (event.payload.uri.scheme !== 'file') {
           return;
         }
-        // FIXME: 暂时不自动添加
-        // this.addFileToContext(event.payload.uri);
+        this.addFileToContext(event.payload.uri, undefined, false);
       }),
     );
 
@@ -88,6 +115,8 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
         if (event.payload.scheme !== 'file') {
           return;
         }
+
+        this.removeFileFromContext(event.payload, false);
       }),
     );
 
@@ -109,11 +138,12 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
           ].sort() as [number, number];
 
           if (selection[0] === selection[1]) {
-            this.addFileToContext(event.payload.editorUri, undefined);
+            this.addFileToContext(event.payload.editorUri, undefined, false);
           } else {
             this.addFileToContext(
               event.payload.editorUri,
               selection.sort((a, b) => a - b),
+              false,
             );
           }
         }
@@ -127,42 +157,51 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
 
   serialize(): SerializedContext {
     const files = this.getAllContextFiles();
-    const recentlyViewFiles = files
-      .filter((v) => !v.selection)
-      .map((file) => {
-        const relativePath = URI.file(this.appConfig.workspaceDir).relative(file.uri);
-        if (relativePath) {
-          return relativePath.toString();
-        }
-        return file.uri.parent.toString();
-      })
-      .filter(Boolean);
-
-    const attachedFiles = files
-      .filter((v) => v.selection)
-      .map((file) => {
-        const ref = this.docModelManager.getModelReference(file.uri);
-        const content = ref!.instance.getText();
-        const lineErrors = this.markerService
-          .getManager()
-          .getMarkers({
-            resource: file.uri.toString(),
-            severities: MarkerSeverity.Error,
-          })
-          .map((marker) => marker.message);
-
-        return {
-          content,
-          lineErrors,
-          path: URI.file(this.appConfig.workspaceDir).relative(file.uri)!.toString(),
-          language: ref?.instance.languageId!,
-        };
-      })
-      .filter(Boolean);
+    const workspaceRoot = URI.file(this.appConfig.workspaceDir);
 
     return {
-      recentlyViewFiles,
-      attachedFiles,
+      recentlyViewFiles: this.serializeRecentlyViewFiles(files.viewed, workspaceRoot),
+      attachedFiles: this.serializeAttachedFiles(files.attached, workspaceRoot),
     };
   }
+
+  private serializeRecentlyViewFiles(files: FileContext[], workspaceRoot: URI): string[] {
+    return files
+      .map((file) => workspaceRoot.relative(file.uri)?.toString() || file.uri.parent.toString())
+      .filter(Boolean);
+  }
+
+  private serializeAttachedFiles(files: FileContext[], workspaceRoot: URI): AttachFileContext[] {
+    return files
+      .map((file) => this.serializeAttachedFile(file, workspaceRoot))
+      .filter(Boolean) as unknown as AttachFileContext[];
+  }
+
+  private serializeAttachedFile(file: FileContext, workspaceRoot: URI) {
+    try {
+      const ref = this.docModelManager.getModelReference(file.uri);
+      if (!ref) {
+        return null;
+      }
+
+      return {
+        content: ref.instance.getText(),
+        lineErrors: this.getFileErrors(file.uri),
+        path: workspaceRoot.relative(file.uri)!.toString(),
+        language: ref.instance.languageId!,
+      };
+    } catch (e) {
+      return null;
+    }
+  }
+
+  private getFileErrors(uri: URI): string[] {
+    return this.markerService
+      .getManager()
+      .getMarkers({
+        resource: uri.toString(),
+        severities: MarkerSeverity.Error,
+      })
+      .map((marker) => marker.message);
+  }
 }
diff --git a/packages/ai-native/src/browser/layout/layout.module.less b/packages/ai-native/src/browser/layout/layout.module.less
index ffa9d0041a..4090b9d5e8 100644
--- a/packages/ai-native/src/browser/layout/layout.module.less
+++ b/packages/ai-native/src/browser/layout/layout.module.less
@@ -14,16 +14,16 @@
       padding: 8px 12px;
       min-width: initial;
       margin: 0;
-
-      ::after {
-        content: '';
-      }
     }
 
     .rce-mbox-text {
       line-height: 18px;
       width: inherit;
       font-size: 12px;
+
+      &::after {
+        display: none;
+      }
     }
 
     .rce-smsg {
diff --git a/packages/ai-native/src/browser/mcp/tools/components/index.module.less b/packages/ai-native/src/browser/mcp/tools/components/index.module.less
index d76736c78f..a61dfef6cb 100644
--- a/packages/ai-native/src/browser/mcp/tools/components/index.module.less
+++ b/packages/ai-native/src/browser/mcp/tools/components/index.module.less
@@ -154,6 +154,7 @@
   background-color: var(--design-chatInput-background);
   padding: 10px;
   border-radius: 4px;
+  margin: 10px 0px;
 
   .command_title {
     display: flex;
diff --git a/packages/ai-native/src/browser/mcp/tools/createNewFileWithText.ts b/packages/ai-native/src/browser/mcp/tools/createNewFileWithText.ts
index a217624f91..79c3b83342 100644
--- a/packages/ai-native/src/browser/mcp/tools/createNewFileWithText.ts
+++ b/packages/ai-native/src/browser/mcp/tools/createNewFileWithText.ts
@@ -27,6 +27,7 @@ export class CreateNewFileWithTextTool implements MCPServerContribution {
   getToolDefinition(): MCPToolDefinition {
     return {
       name: 'create_new_file_with_text',
+      label: 'Create File',
       description:
         'Creates a new file at the specified path within the project directory and populates it with the provided text. ' +
         'Use this tool to generate new files in your project structure. ' +
diff --git a/packages/ai-native/src/browser/mcp/tools/getDiagnosticsByPath.ts b/packages/ai-native/src/browser/mcp/tools/getDiagnosticsByPath.ts
index 0734ba6124..b07c4b4232 100644
--- a/packages/ai-native/src/browser/mcp/tools/getDiagnosticsByPath.ts
+++ b/packages/ai-native/src/browser/mcp/tools/getDiagnosticsByPath.ts
@@ -29,6 +29,7 @@ export class GetDiagnosticsByPathTool implements MCPServerContribution {
   getToolDefinition(): MCPToolDefinition {
     return {
       name: 'get_diagnostics_by_path',
+      label: 'Get Diagnostics',
       description:
         'Retrieves diagnostic information (errors, warnings, etc.) from a specific file in the project. ' +
         'Use this tool to get information about problems in any project file. ' +
diff --git a/packages/ai-native/src/browser/mcp/tools/handlers/RunCommand.ts b/packages/ai-native/src/browser/mcp/tools/handlers/RunCommand.ts
index 7efaab6c4a..9667cdc24a 100644
--- a/packages/ai-native/src/browser/mcp/tools/handlers/RunCommand.ts
+++ b/packages/ai-native/src/browser/mcp/tools/handlers/RunCommand.ts
@@ -49,6 +49,7 @@ export class RunCommandHandler {
   }
 
   async handler(args: z.infer<typeof inputSchema> & { toolCallId: string }, logger: MCPLogger) {
+    logger.appendLine(`Executing command: ${args.command}`);
     if (args.require_user_approval) {
       const def = new Deferred<boolean>();
       this.approvalDeferredMap.set(args.toolCallId, def);
@@ -89,6 +90,7 @@ export class RunCommandHandler {
         content: result,
       });
 
+      logger.appendLine(`Command ${args.command} finished with exit code: ${e.code}`);
       terminalClient.term.writeln(
         `\n${color.italic}> Command ${args.command} executed successfully. Terminal will close in ${
           3000 / 1000
diff --git a/packages/ai-native/src/browser/mcp/tools/runTerminalCmd.ts b/packages/ai-native/src/browser/mcp/tools/runTerminalCmd.ts
index 534fae497d..682d3fea1c 100644
--- a/packages/ai-native/src/browser/mcp/tools/runTerminalCmd.ts
+++ b/packages/ai-native/src/browser/mcp/tools/runTerminalCmd.ts
@@ -32,6 +32,7 @@ export class RunTerminalCommandTool implements MCPServerContribution {
   getToolDefinition(): MCPToolDefinition {
     return {
       name: 'run_terminal_cmd',
+      label: 'Run Command',
       description:
         "PROPOSE a command to run on behalf of the user.\nIf you have this tool, note that you DO have the ability to run commands directly on the USER's system.\n\nAdhere to these rules:\n1. Based on the contents of the conversation, you will be told if you are in the same shell as a previous step or a new shell.\n2. If in a new shell, you should `cd` to the right directory and do necessary setup in addition to running the command.\n3. If in the same shell, the state will persist, no need to do things like `cd` to the same directory.\n4. For ANY commands that would use a pager, you should append ` | cat` to the command (or whatever is appropriate). You MUST do this for: git, less, head, tail, more, etc.\n5. For commands that are long running/expected to run indefinitely until interruption, please run them in the background. To run jobs in the background, set `is_background` to true rather than changing the details of the command.\n6. Dont include any newlines in the command.",
       inputSchema,
diff --git a/packages/ai-native/src/browser/preferences/schema.ts b/packages/ai-native/src/browser/preferences/schema.ts
index ee3dffc9f6..a117f18a95 100644
--- a/packages/ai-native/src/browser/preferences/schema.ts
+++ b/packages/ai-native/src/browser/preferences/schema.ts
@@ -148,7 +148,6 @@ export const aiNativePreferenceSchema: PreferenceSchema = {
     },
     [AINativeSettingSectionsId.SystemPrompt]: {
       type: 'string',
-      default: '',
       description: localize('preference.ai.native.chat.system.prompt.description'),
     },
   },
diff --git a/packages/ai-native/src/common/llm-context.ts b/packages/ai-native/src/common/llm-context.ts
index 6dc409694c..17278a934a 100644
--- a/packages/ai-native/src/common/llm-context.ts
+++ b/packages/ai-native/src/common/llm-context.ts
@@ -15,13 +15,13 @@ export interface LLMContextService {
    */
   cleanFileContext(): void;
 
-  onDidContextFilesChangeEvent: Event<FileContext[]>;
+  onDidContextFilesChangeEvent: Event<{ viewed: FileContext[]; attached: FileContext[]; version: number }>;
 
   /**
    * 从 context 中移除文件
    * @param uri URI
    */
-  removeFileFromContext(uri: URI): void;
+  removeFileFromContext(uri: URI, isManual?: boolean): void;
 
   /** 导出为可序列化格式 */
   serialize(): SerializedContext;
@@ -30,12 +30,18 @@ export interface LLMContextService {
 export interface FileContext {
   uri: URI;
   selection?: [number, number];
-  isManual: boolean;
 }
 
 export const LLMContextServiceToken = Symbol('LLMContextService');
 
+export interface AttachFileContext {
+  content: string;
+  lineErrors: string[];
+  path: string;
+  language: string;
+}
+
 export interface SerializedContext {
   recentlyViewFiles: string[];
-  attachedFiles: Array<{ content: string; lineErrors: string[]; path: string; language: string }>;
+  attachedFiles: Array<AttachFileContext>;
 }
diff --git a/packages/ai-native/src/common/prompts/context-prompt-provider.ts b/packages/ai-native/src/common/prompts/context-prompt-provider.ts
index d081b3d0fc..8934a5eab1 100644
--- a/packages/ai-native/src/common/prompts/context-prompt-provider.ts
+++ b/packages/ai-native/src/common/prompts/context-prompt-provider.ts
@@ -1,5 +1,5 @@
-import { Injectable } from '@opensumi/di';
-import { MaybePromise } from '@opensumi/ide-core-common/lib/utils';
+import { Autowired, Injectable } from '@opensumi/di';
+import { WorkbenchEditorService } from '@opensumi/ide-editor/lib/common/editor';
 
 import { SerializedContext } from '../llm-context';
 
@@ -10,37 +10,46 @@ export interface ChatAgentPromptProvider {
    * 提供上下文提示
    * @param context 上下文
    */
-  provideContextPrompt(context: SerializedContext, userMessage: string): MaybePromise<string>;
+  provideContextPrompt(context: SerializedContext, userMessage: string): string;
 }
 
 @Injectable()
 export class DefaultChatAgentPromptProvider implements ChatAgentPromptProvider {
-  provideContextPrompt(context: SerializedContext, userMessage: string): MaybePromise<string> {
+  @Autowired(WorkbenchEditorService)
+  protected readonly workbenchEditorService: WorkbenchEditorService;
+
+  provideContextPrompt(context: SerializedContext, userMessage: string): string {
+    const editor = this.workbenchEditorService.currentEditor;
+    const currentModel = editor?.currentDocumentModel;
     return `
-          <additional_data>
-          Below are some potentially helpful/relevant pieces of information for figuring out to respond
-          <recently_viewed_files>
-          ${context.recentlyViewFiles.map((file, idx) => `${idx + 1} : ${file}`)}
-          </recently_viewed_files>
-          <attached_files>
-          ${context.attachedFiles.map(
-            (file) =>
-              `
-          <file_contents>
-          \`\`\`${file.language} ${file.path}
-          ${file.content}
-          \`\`\`
-          </file_contents>
-          <linter_errors>
-          ${file.lineErrors.join('\n')}
-          </linter_errors>
-          `,
-          )}
-          
-          </attached_files>
-          </additional_data>
-          <user_query>
-          ${userMessage}
-          </user_query>`;
+<additional_data>
+  Below are some potentially helpful/relevant pieces of information for figuring out to respond
+  <recently_viewed_files>
+${context.recentlyViewFiles.map((file, idx) => `    ${idx + 1}: ${file}`).join('\n')}
+  </recently_viewed_files>
+  <attached_files>
+    ${context.attachedFiles.map(
+      (file) =>
+        `
+    <file_contents>
+    \`\`\`${file.path}
+    ${file.content}
+    \`\`\`
+    </file_contents>
+    <linter_errors>
+    ${file.lineErrors.join('\n')}
+    </linter_errors>
+              `,
+    )}
+  </attached_files>
+${currentModel ? `<current_opened_file>
+  \`\`\`${currentModel.languageId} ${currentModel.uri.toString()}
+${currentModel.getText()}
+  \`\`\`
+  </current_opened_file>` : ''}
+</additional_data>
+<user_query>
+${userMessage}
+</user_query>`;
   }
 }
diff --git a/packages/components/src/popover/index.tsx b/packages/components/src/popover/index.tsx
index 04bcd4232b..a0ee39079c 100644
--- a/packages/components/src/popover/index.tsx
+++ b/packages/components/src/popover/index.tsx
@@ -62,7 +62,7 @@ export const Popover: React.FC<IPopoverProps> = ({
   overlayClassName,
   overlayStyle,
   action,
-  delay,
+  delay = 200,
   zIndex = 1000,
   onClickAction,
   onVisibleChange,
diff --git a/packages/utils/src/platform.ts b/packages/utils/src/platform.ts
index 250f72f464..7d82dcedb9 100644
--- a/packages/utils/src/platform.ts
+++ b/packages/utils/src/platform.ts
@@ -85,7 +85,7 @@ if (typeof navigator === 'object' && !isNodeNavigator && !isElectronRenderer) {
       // VSCode's default language is 'en'
       _language = resolved ? resolved : LANGUAGE_DEFAULT;
       _translationsConfigFile = nlsConfig._translationsConfigFile;
-    } catch (e) {}
+    } catch (e) { }
   }
   _isNative = true;
 }
@@ -134,6 +134,16 @@ export function isRootUser(): boolean {
   return _isNative && !_isWindows && process.getuid() === 0;
 }
 
+export function getOperatingSystemName() {
+  if (isWindows) {
+    return 'Windows';
+  }
+  if (isMacintosh) {
+    return 'Macintosh';
+  }
+  return 'Linux';
+}
+
 /**
  * The language used for the user interface. The format of
  * the string is all lower case (e.g. zh-tw for Traditional


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>packages/ai-native/src/common/llm-context.ts (1)</summary><blockquote>

`46-46`: **将 `attachedFiles` 类型替换为 `AttachFileContext[]`**

原先为行内定义；统一改成接口后，更易维护和扩展。请确保序列化、反序列化逻辑在其他地方也同步更新。

</blockquote></details>
<details>
<summary>packages/ai-native/src/browser/context/llm-context.service.ts (4)</summary><blockquote>

`37-47`: **新增私有方法 `addFileToList`**

封装了插入和裁剪列表逻辑，易于维护；不过 `list.shift()` 会移除最早插入的项，请确认这样处理符合预期，避免误删除较为重要的文件。

---

`62-75`: **通知上下文变更与清理附加文件**

- `notifyContextChange` 统一触发 `onDidContextFilesChangeEmitter`，有助于事件订阅方获取最新文件上下文。  
- `cleanFileContext` 仅清空 `attachedFiles`，保留 `recentlyViewFiles`，避免“最近查看”丢失；如需进一步清理，也需在设计上明确。

---

`161-165`: **序列化方法：`serializeRecentlyViewFiles` 与 `serializeAttachedFiles`**

- `map(...).filter(Boolean)` 可以安全过滤无效项，但如需调试原因，建议记录日志。  
- `serializeAttachedFiles` 显示转换为 `AttachFileContext[]`，若后续增添字段，需同步修改此方法。  



Also applies to: 167-171

---

`191-199`: **私有方法 `getFileErrors`**

- 根据 MarkerSeverity.Error 查找编辑器标记，可明确获取当前文件的错误信息。  
- 若需警告或信息级别的标记，也可扩展此方法以实现更丰富的上下文提示。

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 83d7b2e19fced880459642d4b62d02516781b373 and cc4e3c8055fb5297e64524cb77250e9b2a11aee8.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `packages/ai-native/src/browser/components/ChatContext/index.tsx` (1 hunks)
* `packages/ai-native/src/browser/context/llm-context.service.ts` (6 hunks)
* `packages/ai-native/src/common/llm-context.ts` (2 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (9)</summary>

* GitHub Check: 🚀🚀🚀 Next Version for pull request
* GitHub Check: unittest (ubuntu-latest, 18.x, jsdom)
* GitHub Check: unittest (ubuntu-latest, 18.x, node)
* GitHub Check: build (ubuntu-latest, 20.x)
* GitHub Check: build (macos-latest, 20.x)
* GitHub Check: unittest (macos-latest, 18.x, jsdom)
* GitHub Check: unittest (macos-latest, 18.x, node)
* GitHub Check: build-windows
* GitHub Check: ubuntu-latest, Node.js 20.x

</details>

<details>
<summary>🔇 Additional comments (12)</summary><blockquote>

<details>
<summary>packages/ai-native/src/browser/components/ChatContext/index.tsx (1)</summary>

`42-42`: **仅传入 `files.attached`**

该改动确保 `updateAddedFiles` 只更新已附加文件列表，逻辑更清晰。请确认组件中确实只需要已附加文件数据，不会影响对“最近查看”文件的管理。

</details>
<details>
<summary>packages/ai-native/src/common/llm-context.ts (2)</summary>

`18-18`: **改动了上下文事件的载荷结构**

将原本的 `Event<FileContext[]>` 改为 `Event<{ viewed: FileContext[]; attached: FileContext[] }>`，允许分开处理已附加与最近查看的文件。请确认所有订阅方已同步适配新的事件类型。

---

`37-42`: **新增 `AttachFileContext` 接口**

定义了附加文件的内容、错误行、路径及语言等字段。此结构有利于更灵活地处理文件信息，但请验证下游对这些字段的使用场景，以免出现空字段或额外依赖。

</details>
<details>
<summary>packages/ai-native/src/browser/context/llm-context.service.ts (9)</summary>

`15-15`: **导入了新接口 `AttachFileContext`**

此处仅增加新接口的 import，便于在序列化和上下文管理中使用。

---

`30-34`: **配置新上限与数组初始化**

- `maxAttachFilesLimit = 10`、`maxViewFilesLimit = 20`：请确认是否符合业务需求，若数值过小或过大可能导致文件处理受限。  
- 初始化两个数组 `attachedFiles` 和 `recentlyViewFiles`，分类逻辑更清晰。

---

`49-60`: **变更 `addFileToContext` 逻辑**

- 默认 `isManual = false`，将文件区分到 `recentlyViewFiles` 与 `attachedFiles`。  
- 若对同一 URI 多次调用，可能覆盖原 selection 信息，请留意需求是否需要保留或合并之前的选区。

---

`78-85`: **`removeFileFromContext` 基于 `isManual` 移除文件**

与 `addFileToContext` 对应，注意手动与自动收集的文件可能混杂，确保外部调用方都正确传入 `isManual` 以免误删文件。

---

`102-103`: **自动收集事件：添加文件到查看列表**

在文档模型创建事件中调用 `addFileToContext(..., false)`，能保证每次打开文件自动进入“最近查看”。如需特殊处理临时文件，请另外判断。

---

`112-113`: **自动收集事件：移除文件**

在文档模型移除事件中调用 `removeFileFromContext(..., false)`，确保不再显示在“最近查看”列表。逻辑简单易懂，暂未发现问题。

---

`134-134`: **编辑器选区判断逻辑**

当选区是单行时（start 行与 end 行相同），会将文件添加到“最近查看”。多行选区则连带区间一起存储。请确认这与实际需求匹配，尤其在大段选区时是否影响性能。  



Also applies to: 139-140

---

`153-153`: **序列化上下文根路径与文件**

- 使用 `URI.file(this.appConfig.workspaceDir)` 作为根目录时，需确保 `workspaceDir` 已正确配置。  
- 分别序列化 `recentlyViewFiles` 与 `attachedFiles`，结构更直观。  



Also applies to: 156-157

---

`173-189`: **私有方法 `serializeAttachedFile`**

- 提取文档内容、语言、行级错误信息，整合到 `AttachFileContext`。  
- 异常捕获返回 `null`，不会阻断流程，如需查看具体错误可考虑日志记录。

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-25T01:53:44Z
user: login: ensorrow
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T06:43:48Z
user: login: Ricbet
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T06:15:57Z
user: login: Aaaaash
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T06:22:44Z
user: login: Aaaaash
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T07:10:07Z
user: login: ensorrow
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T10:29:02Z
user: login: Ricbet
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-04T11:38:58Z
user: login: ensorrow
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T12:18:04Z

Review comments:
diff_hunk: @@ -84,33 +82,8 @@ export const InteractiveInput = React.forwardRef(
     }, [props.value, internalValue, internalRef]);
 
     useEffect(() => {
-      if (isUndefined(height)) {
-        return;
-      }
-
-      if (height !== wrapperHeight) {
-        setWrapperHeight(height);
-      }
-    }, [height, wrapperHeight, onHeightChange]);
-
-    useEffect(() => {
-      if (!internalValue) {
-        setWrapperHeight(DEFAULT_HEIGHT);
-        return;
-      }
-
-      if (internalRef && internalRef.current && wrapperHeight <= MAX_WRAPPER_HEIGHT) {
-        internalRef.current.style.height = 0 + 'px';
-        const scrollHeight = internalRef.current.scrollHeight;
-        internalRef.current.style.height = Math.min(scrollHeight, MAX_WRAPPER_HEIGHT) + 'px';
-        const wrapperHeight = Math.min(scrollHeight + 12, MAX_WRAPPER_HEIGHT);
-        setWrapperHeight(wrapperHeight);
-      }
-    }, [internalRef, internalValue, onHeightChange, wrapperHeight]);
-
-    useEffect(() => {
-      onHeightChange?.(wrapperHeight);
-    }, [wrapperHeight]);
path: packages/core-browser/src/components/ai-native/interactive-input/index.tsx
user: login: Ricbet
body: 为什么要删掉这些？现在 inline chat 的 input 用了这个组件
created_at: 2025-03-04T06:13:59Z
author_association: MEMBER
diff_hunk: @@ -84,33 +82,8 @@ export const InteractiveInput = React.forwardRef(
     }, [props.value, internalValue, internalRef]);
 
     useEffect(() => {
-      if (isUndefined(height)) {
-        return;
-      }
-
-      if (height !== wrapperHeight) {
-        setWrapperHeight(height);
-      }
-    }, [height, wrapperHeight, onHeightChange]);
-
-    useEffect(() => {
-      if (!internalValue) {
-        setWrapperHeight(DEFAULT_HEIGHT);
-        return;
-      }
-
-      if (internalRef && internalRef.current && wrapperHeight <= MAX_WRAPPER_HEIGHT) {
-        internalRef.current.style.height = 0 + 'px';
-        const scrollHeight = internalRef.current.scrollHeight;
-        internalRef.current.style.height = Math.min(scrollHeight, MAX_WRAPPER_HEIGHT) + 'px';
-        const wrapperHeight = Math.min(scrollHeight + 12, MAX_WRAPPER_HEIGHT);
-        setWrapperHeight(wrapperHeight);
-      }
-    }, [internalRef, internalValue, onHeightChange, wrapperHeight]);
-
-    useEffect(() => {
-      onHeightChange?.(wrapperHeight);
-    }, [wrapperHeight]);
path: packages/core-browser/src/components/ai-native/interactive-input/index.tsx
user: login: Ricbet
body: 删掉后变这样了![image](https://github.com/user-attachments/assets/a5a74759-001e-4867-a0f8-ff9577130415)
created_at: 2025-03-04T06:14:12Z
author_association: MEMBER
diff_hunk: @@ -84,33 +82,8 @@ export const InteractiveInput = React.forwardRef(
     }, [props.value, internalValue, internalRef]);
 
     useEffect(() => {
-      if (isUndefined(height)) {
-        return;
-      }
-
-      if (height !== wrapperHeight) {
-        setWrapperHeight(height);
-      }
-    }, [height, wrapperHeight, onHeightChange]);
-
-    useEffect(() => {
-      if (!internalValue) {
-        setWrapperHeight(DEFAULT_HEIGHT);
-        return;
-      }
-
-      if (internalRef && internalRef.current && wrapperHeight <= MAX_WRAPPER_HEIGHT) {
-        internalRef.current.style.height = 0 + 'px';
-        const scrollHeight = internalRef.current.scrollHeight;
-        internalRef.current.style.height = Math.min(scrollHeight, MAX_WRAPPER_HEIGHT) + 'px';
-        const wrapperHeight = Math.min(scrollHeight + 12, MAX_WRAPPER_HEIGHT);
-        setWrapperHeight(wrapperHeight);
-      }
-    }, [internalRef, internalValue, onHeightChange, wrapperHeight]);
-
-    useEffect(() => {
-      onHeightChange?.(wrapperHeight);
-    }, [wrapperHeight]);
path: packages/core-browser/src/components/ai-native/interactive-input/index.tsx
user: login: Aaaaash
body: 😯 没注意这两个竟然是同一个
created_at: 2025-03-04T06:22:40Z
author_association: MEMBER
diff_hunk: @@ -84,33 +82,8 @@ export const InteractiveInput = React.forwardRef(
     }, [props.value, internalValue, internalRef]);
 
     useEffect(() => {
-      if (isUndefined(height)) {
-        return;
-      }
-
-      if (height !== wrapperHeight) {
-        setWrapperHeight(height);
-      }
-    }, [height, wrapperHeight, onHeightChange]);
-
-    useEffect(() => {
-      if (!internalValue) {
-        setWrapperHeight(DEFAULT_HEIGHT);
-        return;
-      }
-
-      if (internalRef && internalRef.current && wrapperHeight <= MAX_WRAPPER_HEIGHT) {
-        internalRef.current.style.height = 0 + 'px';
-        const scrollHeight = internalRef.current.scrollHeight;
-        internalRef.current.style.height = Math.min(scrollHeight, MAX_WRAPPER_HEIGHT) + 'px';
-        const wrapperHeight = Math.min(scrollHeight + 12, MAX_WRAPPER_HEIGHT);
-        setWrapperHeight(wrapperHeight);
-      }
-    }, [internalRef, internalValue, onHeightChange, wrapperHeight]);
-
-    useEffect(() => {
-      onHeightChange?.(wrapperHeight);
-    }, [wrapperHeight]);
path: packages/core-browser/src/components/ai-native/interactive-input/index.tsx
user: login: Aaaaash
body: revert 了
created_at: 2025-03-04T07:10:07Z
author_association: MEMBER
diff_hunk: @@ -119,10 +140,28 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
       throw new Error(`No agent with id ${id}`);
     }
 
+    // 发送第一条消息时携带初始 context
+    if (!this.initialUserMessageMap.has(id)) {
path: packages/ai-native/src/browser/chat/chat-agent.service.ts
user: login: ensorrow
body: 应该用 sessionId 来判断？
created_at: 2025-03-04T10:25:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -194,12 +205,29 @@ export const ChatInput = React.forwardRef((props: IChatInputProps, ref) => {
   const [isExpand, setIsExpand] = useState(false);
   const [placeholder, setPlaceHolder] = useState(localize('aiNative.chat.input.placeholder.default'));
 
+  const dialogService = useInjectable<IDialogService>(IDialogService);
+  const aiNativeConfigService = useInjectable<AINativeConfigService>(AINativeConfigService);
+  const mcpServerProxyService = useInjectable<MCPServerProxyService>(TokenMCPServerProxyService);
   const monacoCommandRegistry = useInjectable<MonacoCommandRegistry>(MonacoCommandRegistry);
   const chatAgentService = useInjectable<IChatAgentService>(IChatAgentService);
   const chatFeatureRegistry = useInjectable<ChatFeatureRegistry>(ChatFeatureRegistryToken);
+  const commandService = useInjectable<CommandService>(CommandService);
 
   const currentAgentIdRef = useLatest(agentId);
 
+  const handleShowMCPConfig = React.useCallback(() => {
+    commandService.executeCommand(OPEN_MCP_CONFIG_COMMAND.id);
+  }, [commandService]);
+
+  const handleShowMCPTools = React.useCallback(async () => {
+    const tools = await mcpServerProxyService.getAllMCPTools();
+    dialogService.open({
+      message: <MCPToolsDialog tools={tools} />,
+      type: MessageType.Empty,
+      buttons: ['关闭'],
path: packages/ai-native/src/browser/components/ChatInput.tsx
user: login: ensorrow
body: ```suggestion
      buttons: [localize('dialog.file.close')],
```
created_at: 2025-03-04T10:27:49Z
author_association: CONTRIBUTOR

PR comments:
user: login: Aaaaash
created_at: 2025-02-25T01:49:56Z
body: /next
author_association: MEMBER
user: login: coderabbitai[bot]
created_at: 2025-02-25T01:53:41Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
本次变更主要在文件处理逻辑上进行调整。在 ChatContext 组件中，更新函数 `updateAddedFiles` 的参数由整个文件对象改为仅传递其 `attached` 属性。同时，在 llm-context 服务及相关公共接口中，对上下文文件进行重构：将单一数组拆分为“已附加”和“最近浏览”两个分类，更新了方法签名、事件 payload 和序列化逻辑，移除了冗余属性，提升了代码结构的清晰度。

## Changes

| 文件 | 改动说明 |
| ---- | -------- |
| `packages/ai-native/.../ChatContext/index.tsx` | 在 `useEffect` 中调用 `updateAddedFiles` 时，仅传入 `files.attached`，而不是整个 `files` 对象。 |
| `packages/ai-native/.../llm-context.service.ts`<br>`packages/ai-native/.../llm-context.ts` | 重构文件上下文管理：将文件分为“已附加”和“最近浏览”两类；修改方法签名及导入；新增辅助方法用于列表添加、序列化和错误处理；更新了事件 payload 与相关接口定义，移除 `isManual` 属性。 |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User as 用户
    participant LLM as LLMContextService
    participant List as addFileToList
    participant Notifier as 事件通知器

    User->>LLM: addFileToContext(uri, selection, isManual)
    LLM->>LLM: 判断文件类型（已附加 vs 最近浏览）
    LLM->>List: 调用 addFileToList 添加文件
    List-->>LLM: 返回更新后的列表
    LLM->>Notifier: 触发上下文文件更新事件
    Notifier-->>LLM: 事件确认
```

## Suggested reviewers
- life2015
- hacke2

<!-- walkthrough_end -->
<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->

> [!WARNING]
> There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.
> 
> <details>
> <summary>🔧 ESLint</summary>
> 
> > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
> 
> yarn install v1.22.22
> [1/4] Resolving packages...
> warning eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options.
> warning eslint > @humanwhocodes/config-array@0.13.0: Use @eslint/config-array instead
> warning eslint > @humanwhocodes/config-array > @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead
> warning eslint > file-entry-cache > flat-cache > rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
> warning eslint > file-entry-cache > flat-cache > rimraf > glob@7.2.3: Glob versions prior to v9 are no longer supported
> warning eslint > file-entry-cache > flat-cache > rimraf > glob > inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
> warning eslint-import-resolver-typescript > glob@7.2.3: Glob versions prior to v9 are no longer supported
> error Couldn't find any versions for "@opensumi/ide-dev-tool" that matches "workspace:*"
> info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.
> 
> 
> 
> </details>

<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings (Beta)

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR. ([Beta](https://docs.coderabbit.ai/early-access#beta))
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: opensumi[bot]
created_at: 2025-02-25T01:54:30Z
body: <!-- versionInfo: PR Next | 3.7.2-next-1740448398.0 -->

🎉 PR Next publish successful!

3.7.2-next-1740448398.0


author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-02-25T02:06:05Z
body: ## [Codecov](https://app.codecov.io/gh/opensumi/core/pull/4407?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi) Report
Attention: Patch coverage is `6.25000%` with `90 lines` in your changes missing coverage. Please review.
> Project coverage is 53.36%. Comparing base [(`820dc75`)](https://app.codecov.io/gh/opensumi/core/commit/820dc755f37cb6f9c13a335b7a18c8ebddfd2449?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi) to head [(`df252f0`)](https://app.codecov.io/gh/opensumi/core/commit/df252f0c72113f9b1ae6e55708c31521a539be51?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi).
> Report is 2 commits behind head on v3.8.

| [Files with missing lines](https://app.codecov.io/gh/opensumi/core/pull/4407?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi) | Patch % | Lines |
|---|---|---|
| [...-native/src/browser/context/llm-context.service.ts](https://app.codecov.io/gh/opensumi/core/pull/4407?src=pr&el=tree&filepath=packages%2Fai-native%2Fsrc%2Fbrowser%2Fcontext%2Fllm-context.service.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi#diff-cGFja2FnZXMvYWktbmF0aXZlL3NyYy9icm93c2VyL2NvbnRleHQvbGxtLWNvbnRleHQuc2VydmljZS50cw==) | 0.00% | [43 Missing and 9 partials :warning: ](https://app.codecov.io/gh/opensumi/core/pull/4407?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi) |
| [...s/ai-native/src/browser/chat/chat-agent.service.ts](https://app.codecov.io/gh/opensumi/core/pull/4407?src=pr&el=tree&filepath=packages%2Fai-native%2Fsrc%2Fbrowser%2Fchat%2Fchat-agent.service.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi#diff-cGFja2FnZXMvYWktbmF0aXZlL3NyYy9icm93c2VyL2NoYXQvY2hhdC1hZ2VudC5zZXJ2aWNlLnRz) | 0.00% | [18 Missing and 3 partials :warning: ](https://app.codecov.io/gh/opensumi/core/pull/4407?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi) |
| [...tive/src/common/prompts/context-prompt-provider.ts](https://app.codecov.io/gh/opensumi/core/pull/4407?src=pr&el=tree&filepath=packages%2Fai-native%2Fsrc%2Fcommon%2Fprompts%2Fcontext-prompt-provider.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi#diff-cGFja2FnZXMvYWktbmF0aXZlL3NyYy9jb21tb24vcHJvbXB0cy9jb250ZXh0LXByb21wdC1wcm92aWRlci50cw==) | 0.00% | [9 Missing and 1 partial :warning: ](https://app.codecov.io/gh/opensumi/core/pull/4407?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi) |
| [packages/utils/src/platform.ts](https://app.codecov.io/gh/opensumi/core/pull/4407?src=pr&el=tree&filepath=packages%2Futils%2Fsrc%2Fplatform.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi#diff-cGFja2FnZXMvdXRpbHMvc3JjL3BsYXRmb3JtLnRz) | 16.66% | [3 Missing and 2 partials :warning: ](https://app.codecov.io/gh/opensumi/core/pull/4407?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi) |
| [...ative/src/browser/mcp/tools/handlers/RunCommand.ts](https://app.codecov.io/gh/opensumi/core/pull/4407?src=pr&el=tree&filepath=packages%2Fai-native%2Fsrc%2Fbrowser%2Fmcp%2Ftools%2Fhandlers%2FRunCommand.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi#diff-cGFja2FnZXMvYWktbmF0aXZlL3NyYy9icm93c2VyL21jcC90b29scy9oYW5kbGVycy9SdW5Db21tYW5kLnRz) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/opensumi/core/pull/4407?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             v3.8    #4407      +/-   ##
==========================================
- Coverage   53.38%   53.36%   -0.03%     
==========================================
  Files        1657     1657              
  Lines      102198   102254      +56     
  Branches    22114    22121       +7     
==========================================
+ Hits        54563    54572       +9     
- Misses      39627    39664      +37     
- Partials     8008     8018      +10     
```

| [Flag](https://app.codecov.io/gh/opensumi/core/pull/4407/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi) | Coverage Δ | |
|---|---|---|
| [jsdom](https://app.codecov.io/gh/opensumi/core/pull/4407/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi) | `48.88% <6.25%> (-0.03%)` | :arrow_down: |
| [node](https://app.codecov.io/gh/opensumi/core/pull/4407/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi) | `12.19% <1.04%> (-0.01%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/opensumi/core/pull/4407?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensumi).

author_association: NONE
user: login: Aaaaash
created_at: 2025-02-25T02:22:46Z
body: /next
author_association: MEMBER
user: login: opensumi[bot]
created_at: 2025-02-25T02:27:23Z
body: <!-- versionInfo: PR Next | 3.7.2-next-1740450374.0 -->

🎉 PR Next publish successful!

3.7.2-next-1740450374.0


author_association: CONTRIBUTOR
user: login: Aaaaash
created_at: 2025-02-25T02:51:18Z
body: /next
author_association: MEMBER
user: login: opensumi[bot]
created_at: 2025-02-25T02:56:01Z
body: <!-- versionInfo: PR Next | 3.8.1-next-1740452092.0 -->

🎉 PR Next publish successful!

3.8.1-next-1740452092.0


author_association: CONTRIBUTOR
user: login: Aaaaash
created_at: 2025-02-25T06:02:43Z
body: /next
author_association: MEMBER
user: login: opensumi[bot]
created_at: 2025-02-25T06:07:18Z
body: <!-- versionInfo: PR Next | 3.8.1-next-1740463566.0 -->

🎉 PR Next publish successful!

3.8.1-next-1740463566.0


author_association: CONTRIBUTOR
user: login: Aaaaash
created_at: 2025-02-25T06:27:04Z
body: /next
author_association: MEMBER
user: login: opensumi[bot]
created_at: 2025-02-25T06:31:46Z
body: <!-- versionInfo: PR Next | 3.8.1-next-1740465035.0 -->

🎉 PR Next publish successful!

3.8.1-next-1740465035.0


author_association: CONTRIBUTOR
user: login: Aaaaash
created_at: 2025-02-25T07:03:06Z
body: /next
author_association: MEMBER
user: login: opensumi[bot]
created_at: 2025-02-25T07:07:51Z
body: <!-- versionInfo: PR Next | 3.8.1-next-1740467196.0 -->

🎉 PR Next publish successful!

3.8.1-next-1740467196.0


author_association: CONTRIBUTOR
user: login: Aaaaash
created_at: 2025-02-25T09:21:39Z
body: /next
author_association: MEMBER
user: login: opensumi[bot]
created_at: 2025-02-25T09:26:27Z
body: <!-- versionInfo: PR Next | 3.8.1-next-1740475512.0 -->

🎉 PR Next publish successful!

3.8.1-next-1740475512.0


author_association: CONTRIBUTOR
user: login: Aaaaash
created_at: 2025-02-25T10:18:47Z
body: /next
author_association: MEMBER
user: login: opensumi[bot]
created_at: 2025-02-25T10:23:38Z
body: <!-- versionInfo: PR Next | 3.8.1-next-1740478950.0 -->

🎉 PR Next publish successful!

3.8.1-next-1740478950.0


author_association: CONTRIBUTOR
user: login: Aaaaash
created_at: 2025-02-28T06:41:46Z
body: /next
author_association: MEMBER
user: login: opensumi[bot]
created_at: 2025-02-28T06:46:18Z
body: <!-- versionInfo: PR Next | 3.8.1-next-1740725107.0 -->

🎉 PR Next publish successful!

3.8.1-next-1740725107.0


author_association: CONTRIBUTOR
user: login: Aaaaash
created_at: 2025-02-28T07:04:24Z
body: /next
author_association: MEMBER
user: login: opensumi[bot]
created_at: 2025-02-28T07:09:05Z
body: <!-- versionInfo: PR Next | 3.8.1-next-1740726474.0 -->

🎉 PR Next publish successful!

3.8.1-next-1740726474.0


author_association: CONTRIBUTOR
user: login: Aaaaash
created_at: 2025-03-03T01:26:42Z
body: /next
author_association: MEMBER
user: login: opensumi[bot]
created_at: 2025-03-03T01:31:40Z
body: <!-- versionInfo: PR Next | 3.8.1-next-1740965430.0 -->

🎉 PR Next publish successful!

3.8.1-next-1740965430.0


author_association: CONTRIBUTOR