mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-04 06:30:53 +00:00
docs: harden tui optimization design
This commit is contained in:
parent
88efd775db
commit
e2be1bd548
7 changed files with 1549 additions and 145 deletions
|
|
@ -1,35 +1,35 @@
|
|||
# TUI 优化:启动性能与 MCP
|
||||
|
||||
> 详细设计文档 1/3 — 解决启动缓慢问题,尤其是配置了 MCP Server 的场景。
|
||||
> 详细设计文档 — 解决启动缓慢问题,尤其是配置了 MCP Server 的场景。
|
||||
|
||||
## 1. 问题分析
|
||||
|
||||
### 1.1 启动流程现状
|
||||
|
||||
启动入口位于 `packages/cli/src/gemini.tsx` 的 `main()` 函数(第 290 行),执行一个包含多段串行等待的初始化管线:
|
||||
启动入口位于 `packages/cli/src/gemini.tsx` 的 `main()` 函数,执行一个包含多段串行等待的初始化管线:
|
||||
|
||||
```
|
||||
T0: profileCheckpoint('main_entry') ← 第 291 行
|
||||
T0: profileCheckpoint('main_entry')
|
||||
│
|
||||
├─ loadSettings() [同步, 读取 4-5 个 JSON] ← 第 293 行
|
||||
├─ cleanupCheckpoints() [异步, 等待完成] ← 第 294 行
|
||||
├─ parseArguments() [异步, yargs 解析] ← 第 297 行
|
||||
├─ dns.setDefaultResultOrder() ← 第 310 行
|
||||
├─ themeManager.loadCustomThemes() [同步] ← 第 315 行
|
||||
├─ loadSettings() [同步, 读取 4-5 个 JSON]
|
||||
├─ cleanupCheckpoints() [异步, 等待完成]
|
||||
├─ parseArguments() [异步, yargs 解析]
|
||||
├─ dns.setDefaultResultOrder()
|
||||
├─ themeManager.loadCustomThemes() [同步]
|
||||
│
|
||||
├─ Sandbox 检查 + 可能的进程重启 ← 第 328-415 行
|
||||
├─ Sandbox 检查 + 可能的进程重启
|
||||
│ ├─ loadSandboxConfig() [异步, 文件 I/O]
|
||||
│ ├─ loadCliConfig() [异步, 仅用于沙箱场景]
|
||||
│ ├─ validateAuth() [异步, 可能触发网络请求]
|
||||
│ └─ start_sandbox() 或 relaunchAppInChildProcess()
|
||||
│
|
||||
├─ loadCliConfig() [异步, 合并所有配置源] ← 第 445 行
|
||||
├─ initializeApp() [异步: i18n + auth + IDE] ← 第 507 行
|
||||
├─ 收集启动警告 ← 第 518-535 行
|
||||
├─ Kitty 协议检测 [异步] ← 第 543 行
|
||||
├─ startInteractiveUI() [渲染 React 树] ← 第 544 行
|
||||
├─ loadCliConfig() [异步, 合并所有配置源]
|
||||
├─ initializeApp() [异步: i18n + auth + IDE]
|
||||
├─ 收集启动警告
|
||||
├─ Kitty 协议检测 [异步]
|
||||
├─ startInteractiveUI() [渲染 React 树]
|
||||
│
|
||||
└─ config.initialize() [UI 渲染后, MCP 发现在此] ← config.ts 第 872 行
|
||||
└─ AppContainer mount 后 effect 中调用 `config.initialize()`
|
||||
├─ FileDiscoveryService 初始化
|
||||
├─ GitService 初始化
|
||||
├─ PromptRegistry 初始化
|
||||
|
|
@ -40,7 +40,7 @@ T0: profileCheckpoint('main_entry') ← 第 291 行
|
|||
|
||||
### 1.2 各阶段耗时分析
|
||||
|
||||
当前启动分析器(`packages/cli/src/utils/startupProfiler.ts`)只记录到 UI render 前后的粗粒度 checkpoint;交互式模式下 `config.initialize()` 是在 `AppContainer` mount 后的 effect 中执行,现有 profile 文件并不会直接覆盖这段耗时。因此下表是**源码路径推导 + 需补充 instrumentation 验证的初始估计**,不能作为最终性能基线。
|
||||
当前启动分析器(`packages/cli/src/utils/startupProfiler.ts`)只记录到 UI render 前后的粗粒度 checkpoint;交互式模式下 `config.initialize()` 是在 `AppContainer` mount 后的 effect 中执行,现有 profile 文件并不会直接覆盖这段耗时。另外,当前 profiler 仅在 `QWEN_CODE_PROFILE_STARTUP=1` 且运行于 sandbox child process 时启用;默认本地开发命令如果不经过该路径,可能不会产出 profile。因此下表是**源码路径推导 + 需补充 instrumentation 验证的初始估计**,不能作为最终性能基线。
|
||||
|
||||
| 阶段 | 估计耗时 | I/O 操作 | 瓶颈类型 |
|
||||
| --------------------------------- | ---------- | ------------------ | ------------ |
|
||||
|
|
@ -68,7 +68,7 @@ T0: profileCheckpoint('main_entry') ← 第 291 行
|
|||
|
||||
1. Settings 加载使用 `fs.readFileSync` 串行读取多个文件(`packages/cli/src/config/settings.ts`)
|
||||
2. `initializeApp()` 依赖 `loadCliConfig()` 产出的 `config`,不能整体并行;可优化的是 i18n 与 `loadCliConfig()` 并行,以及 config 就绪后 auth、startup warnings、Kitty 检测等独立步骤并行
|
||||
3. MCP 发现跨 Server 并行(`Promise.all`),但 `discoverAllMcpTools()` 仍等待所有 Server settle 后才把 discovery state 标记为完成;UI 只能看到整体完成/失败语义,缺少首工具和逐 Server 可用指标
|
||||
3. MCP 发现跨 Server 并行(`Promise.all`),但 `discoverAllMcpTools()` 仍等待所有 Server settle 后才把 discovery state 标记为完成;当前 UI 已能显示 `connected/total` 的连接进度,但仍缺少首工具注册、逐 Server ready、Gemini tools 已刷新等更贴近“可用性”的指标
|
||||
4. `McpClient.discover()` 内部会在单个 Server discover 完成时注册工具,并非“所有 Server 完成后才统一注册”;真正缺口是 ToolRegistry 的渐进刷新语义、Gemini tools declaration 的 debounce 更新,以及慢 Server 对整体完成状态的拖延
|
||||
5. MCP 默认超时 10 分钟(`MCP_DEFAULT_TIMEOUT_MSEC = 10 * 60 * 1000`),需要区分“发现超时”和“工具调用超时”,不能简单把所有 MCP timeout 全局缩短到 30 秒
|
||||
|
||||
|
|
@ -112,15 +112,37 @@ async discoverAllMcpTools(cliConfig: Config): Promise<void> {
|
|||
- 单个 Server 的工具会在 `client.discover(cliConfig)` 完成时注册,但 ToolRegistry 没有对外暴露稳定的“server ready / tools changed”事件语义
|
||||
- `GeminiClient.setTools()` 只在 chat 初始化或显式调用时刷新 tools declaration;后续 MCP 工具动态加入后,如果不额外调用,模型不会自动拿到新工具
|
||||
- `ToolRegistry.discoverMcpTools()` 当前会先清理 discovered tools/prompts,不适合直接作为 fire-and-forget 的渐进发现入口
|
||||
- 源码中已存在 `discoverAllMcpToolsIncremental()` 与 `discoverToolsForServer()` 这两块增量基础设施,但前者尚未接入启动主路径,且还不检测 server config 变化
|
||||
- 运行期 refresh 仍会走 `ExtensionManager.refreshMemory()/refreshTools()` → `restartMcpServers()` 的全量重启路径,因此如果只优化冷启动,插件/技能刷新时依然会回退到全量 rediscovery
|
||||
- 默认超时 10 分钟对 discovery 过长,但对长耗时 tool call 可能合理,必须拆开配置和默认值
|
||||
- 发现流程在 `config.initialize()` → `createToolRegistry()` → `registry.discoverAllTools()` 调用链中被前置初始化步骤阻塞
|
||||
|
||||
### 1.4 Gemini CLI / Claude Code 调研结论
|
||||
|
||||
外部源码调研补充了三条对本设计非常关键的事实:
|
||||
|
||||
1. **Gemini CLI 已经证明“UI 先起来、MCP 后补齐”是可产品化的**
|
||||
`packages/cli/src/gemini.tsx` 会延迟加载交互 UI,`packages/cli/src/ui/AppContainer.tsx` 中的 `config.initialize()` 则放到 mount 后执行;同时 UI 通过 `useMcpStatus()` 和明确提示文案告诉用户 MCP 仍在初始化,prompt 会排队。对 qwen-code 的意义是:渐进式 MCP 可用性不只是内部时序优化,而是一个明确的用户体验模型。
|
||||
|
||||
2. **Claude Code 把冷启动优化前移到了模块求值阶段**
|
||||
`src/main.tsx` 在大部分 imports 之前就启动 `startMdmRawRead()`、`startKeychainPrefetch()` 等后台工作,并大量使用 feature-gated `require()` 把冷路径模块排除在首屏之外。这说明 qwen-code 的“产物体积优化”不应只放在远期,应把“入口延迟加载 + 冷路径裁剪”前移到 P0/P1。
|
||||
|
||||
3. **MCP 生命周期不应只看首次 discover**
|
||||
Claude 的 `useManageMCPConnections.ts` 不只处理首连,还处理 16ms 批量状态更新、`ToolListChanged` / `PromptListChanged` / `ResourceListChanged`、远端 transport 自动重连。对 qwen-code 的意义是:MCP 设计必须从“启动 discover”扩展为“运行期持续变更管理”。
|
||||
|
||||
## 2. 解决方案
|
||||
|
||||
### 2.0 [P0] 启动观测基线先行
|
||||
|
||||
**目标**:先把启动过程拆成可验证的指标,再执行并行化和 MCP 渐进加载,避免用 render 前 checkpoint 推断 render 后瓶颈。
|
||||
|
||||
**实现前提校准**:
|
||||
|
||||
- 当前 profiler 仅在 `QWEN_CODE_PROFILE_STARTUP=1` 且 `SANDBOX` child process 中启用
|
||||
- 如果要让这套文档成为日常可执行的基线方案,需要二选一:
|
||||
1. 保持现状,但把所有基准采集都放到 sandbox child process 中执行
|
||||
2. 扩展 profiler,使其支持受控的非 sandbox/dev 采集模式,并避免父子进程重复记录
|
||||
|
||||
**新增 checkpoint/event**:
|
||||
|
||||
| 指标 | 触发位置 | 用途 |
|
||||
|
|
@ -175,6 +197,8 @@ async discoverAllMcpTools(cliConfig: Config): Promise<void> {
|
|||
|
||||
```bash
|
||||
QWEN_CODE_PROFILE_STARTUP=1 qwen-code --prompt "test"
|
||||
# 注意:当前实现要求 profile 运行在 sandbox child process;如果本地命令路径未进入 sandbox,
|
||||
# 需要先扩展 profiler 或使用能确保进入 child process 的测试方式
|
||||
# 对比 after_load_settings 阶段耗时
|
||||
```
|
||||
|
||||
|
|
@ -223,6 +247,49 @@ const [_auth, startupWarnings, userWarnings, _kitty] = await Promise.all([
|
|||
|
||||
**预期收益**:`before_render` checkpoint 耗时减少 200-400ms(主要来自 i18n 与 config 并行 + auth 与警告/检测并行)。
|
||||
|
||||
### 2.2A [P0] 入口延迟加载与冷路径裁剪
|
||||
|
||||
**动机**:Gemini CLI 在入口动态导入 `interactiveCli.js`,Claude Code 则通过顶层并行预取 + feature-gated require 把大量非关键模块留在冷路径之外。qwen-code 当前主入口仍偏“全部先加载,再决定要不要用”,会放大 bundle 解析和模块求值成本。
|
||||
|
||||
**方案**:
|
||||
|
||||
1. 交互模式相关模块改为动态导入
|
||||
- `Ink`
|
||||
- `AppContainer`
|
||||
- 大型 UI hooks / layouts / themes
|
||||
2. 将纯 CLI / 非交互路径与交互路径拆分 chunk
|
||||
3. 对实验特性、远期 UI 能力、重依赖组件使用 feature flag 或运行时懒加载
|
||||
4. 将“首屏前必须完成”的代码限制为:
|
||||
- 参数解析
|
||||
- settings / config 主路径
|
||||
- auth 最小必要路径
|
||||
- 进入交互 render 所需的最小依赖
|
||||
|
||||
**示意**:
|
||||
|
||||
```typescript
|
||||
// 当前:入口直接求值全部 UI 模块
|
||||
import './interactiveCli.js';
|
||||
|
||||
// 目标:确认进入交互模式后再加载
|
||||
if (isInteractive) {
|
||||
const { startInteractiveUI } = await import('./interactiveCli.js');
|
||||
await startInteractiveUI(...);
|
||||
}
|
||||
```
|
||||
|
||||
**影响范围**:
|
||||
|
||||
- `packages/cli/src/gemini.tsx`
|
||||
- `packages/cli/src/ui/*` 的顶层 import 组织方式
|
||||
- 构建产物分析脚本 / bundle report
|
||||
|
||||
**预期收益**:
|
||||
|
||||
- 降低 `processUptimeAtT0Ms`
|
||||
- 降低 UI 首屏前的模块求值时间
|
||||
- 为后续引入 `marked`、更复杂高亮或虚拟滚动组件预留体积空间
|
||||
|
||||
### 2.3 [P1] 渐进式 MCP 可用性
|
||||
|
||||
**现状校准**:
|
||||
|
|
@ -231,6 +298,8 @@ const [_auth, startupWarnings, userWarnings, _kitty] = await Promise.all([
|
|||
- 但 `discoverAllMcpTools()` 仍等待所有 Server settle 后才完成,慢 Server 会拖延整体 discovery state、初始化完成语义和 UI 反馈
|
||||
- `ToolRegistry.discoverMcpTools()` 会先 `removeDiscoveredTools()` 并清空 prompt registry,不适合作为异步 fire-and-forget 入口,否则可能短暂移除已可用工具
|
||||
- `GeminiClient.setTools()` 不会在 MCP 工具动态加入时自动触发;不刷新 tools declaration 时,模型下一次请求仍可能看不到新工具
|
||||
- `McpClientManager` 已有 `discoverAllMcpToolsIncremental()`,`ToolRegistry` 已有 `discoverToolsForServer()`;第一阶段应优先复用这些 primitives,而不是重写整套 client 生命周期
|
||||
- 当前 `ConfigInitDisplay` 已显示 `connected/total`,因此设计目标应从“做出进度 UI”升级为“把连接进度补齐成工具可用性语义”
|
||||
|
||||
**方案**:
|
||||
|
||||
|
|
@ -241,10 +310,18 @@ const [_auth, startupWarnings, userWarnings, _kitty] = await Promise.all([
|
|||
5. **合理超时**:拆分 discovery timeout 与 tool-call timeout。discovery 默认可降至 30 秒;tool call 继续尊重 `MCP_DEFAULT_TIMEOUT_MSEC` 或 server 配置,避免误杀长耗时工具
|
||||
6. **UI 进度指示**:复用现有 `mcp-client-update` 事件,显示 "N/M MCP Servers 已连接 / 失败 / 超时",并在 init 后持续更新
|
||||
|
||||
**核心代码变更**(`packages/core/src/tools/mcp-client-manager.ts`):
|
||||
**推荐实现路径**:
|
||||
|
||||
第一阶段不要从零重写 `McpClient` 生命周期,而是在现有基础上补齐三层缺口:
|
||||
|
||||
1. 启动路径接入 `skipDiscovery` + 增量发现
|
||||
2. ToolRegistry 暴露不清空全局 discovered tools 的 incremental wrapper
|
||||
3. 每个 server ready 后 debounce `GeminiClient.setTools()`
|
||||
|
||||
**核心代码变更**(建议基于现有 `discoverAllMcpToolsIncremental()` / `discoverToolsForServer()` 演进):
|
||||
|
||||
```typescript
|
||||
async discoverMcpToolsProgressively(
|
||||
async discoverMcpToolsIncrementally(
|
||||
cliConfig: Config,
|
||||
onServerReady?: (name: string) => void,
|
||||
onToolsChanged?: () => void,
|
||||
|
|
@ -260,7 +337,8 @@ async discoverMcpToolsProgressively(
|
|||
await Promise.race([
|
||||
(async () => {
|
||||
// 只清理当前 server 的旧工具/prompt,不能清空全局 discovered tools
|
||||
this.toolRegistry.removeDiscoveredToolsForServer(name);
|
||||
this.toolRegistry.removeMcpToolsByServer(name);
|
||||
cliConfig.getPromptRegistry().removePromptsByServer(name);
|
||||
await client.connect();
|
||||
await client.discover(cliConfig);
|
||||
onServerReady?.(name);
|
||||
|
|
@ -279,7 +357,7 @@ async discoverMcpToolsProgressively(
|
|||
|
||||
**实现路径**:
|
||||
|
||||
代码审查发现 `createToolRegistry()` 已支持 `skipDiscovery` 选项,但不能直接 fire-and-forget 调用现有 `discoverMcpTools()`,因为它会清理所有 discovered tools/prompts。应实现一个新的渐进入口或扩展现有 per-server 发现入口:
|
||||
代码审查发现 `createToolRegistry()` 已支持 `skipDiscovery` 选项,但不能直接 fire-and-forget 调用现有 `discoverMcpTools()`,因为它会清理所有 discovered tools/prompts。更务实的方式是为 ToolRegistry 增加 incremental wrapper,内部复用现有 manager/per-server 发现入口,而不是完全另起炉灶:
|
||||
|
||||
```typescript
|
||||
// 阶段 1:快速创建工具注册表(跳过 MCP discovery)
|
||||
|
|
@ -287,7 +365,7 @@ await createToolRegistry({ skipDiscovery: true });
|
|||
|
||||
// 阶段 2:异步 MCP 发现;server ready 后 debounce 刷新 Gemini tools
|
||||
const refreshGeminiTools = debounce(() => config.getGeminiClient().setTools(), 100);
|
||||
void toolRegistry.discoverMcpToolsProgressively({
|
||||
void toolRegistry.discoverMcpToolsIncrementally({
|
||||
onServerReady,
|
||||
onToolsChanged: refreshGeminiTools,
|
||||
});
|
||||
|
|
@ -295,9 +373,9 @@ void toolRegistry.discoverMcpToolsProgressively({
|
|||
|
||||
**影响范围**:
|
||||
|
||||
- `packages/core/src/tools/mcp-client-manager.ts` — 添加渐进发现入口、逐 Server 超时控制、server ready 事件
|
||||
- `packages/core/src/tools/mcp-client-manager.ts` — 复用并扩展现有 incremental/per-server 发现入口,补 server config change 检测、逐 Server 超时控制、server ready 事件
|
||||
- `packages/core/src/config/config.ts` — 利用已有的 `skipDiscovery` 选项,在 `initialize()` 中跳过 MCP,另行启动
|
||||
- `packages/core/src/tools/tool-registry.ts` — 添加不清空全局 discovered tools 的 per-server discover/replace API
|
||||
- `packages/core/src/tools/tool-registry.ts` — 添加不清空全局 discovered tools 的 incremental wrapper / per-server discover-replace API
|
||||
- `packages/core/src/core/client.ts` — 暴露或复用 `setTools()`,支持 debounce 刷新
|
||||
- `packages/cli/src/ui/AppContainer.tsx` / `ConfigInitDisplay.tsx` — 扩展 MCP 连接状态显示到初始化后
|
||||
|
||||
|
|
@ -314,6 +392,37 @@ void toolRegistry.discoverMcpToolsProgressively({
|
|||
- 超时降低可能导致网络慢的环境误判 Server 不可用,应只作用于 discovery,并保留配置项允许用户调整
|
||||
- per-server 替换必须是原子的,避免短暂删除其他 Server 工具或 prompts
|
||||
|
||||
### 2.3A [P1/P2] 运行期 MCP refresh/reload 路径增量化
|
||||
|
||||
**现状**:除冷启动外,运行期的 tools/memory refresh 仍会走全量重启路径:
|
||||
|
||||
- `ExtensionManager.refreshMemory()`
|
||||
- `ExtensionManager.refreshTools()`
|
||||
- `ToolRegistry.restartMcpServers()`
|
||||
|
||||
这条链路当前仍等价于“清空 discovered MCP tools/prompts 后重新 discover 全部 server”。如果只优化启动阶段,`/reload-plugins`、技能/扩展刷新、某些设置变更后的体验依然会出现全量抖动。
|
||||
|
||||
**方案**:
|
||||
|
||||
1. 为 refresh 路径增加“配置 diff → changed server set”计算
|
||||
2. 未变化的 server 保持连接与工具集合,不重复 discover
|
||||
3. 新增/断线/配置变化的 server 走 per-server replace
|
||||
4. server 移除时只移除该 server 的 tools/prompts
|
||||
5. 运行期批量更新继续复用同一套 `toolRegistryChanged` / debounce `setTools()` 机制
|
||||
|
||||
**为什么要单列这一节**:
|
||||
|
||||
- 这不是冷启动优化的附属项,而是让 MCP 设计真正从“一次性 startup task”升级为“生命周期系统”的关键
|
||||
- Claude Code 的调研表明,list-changed、reconnect、batch flush 都属于同一个运行期问题空间
|
||||
- 如果这一层不设计,文档里关于“渐进式 MCP 可用性”的收益会只存在于首次启动
|
||||
|
||||
**影响范围**:
|
||||
|
||||
- `packages/core/src/extension/extensionManager.ts`
|
||||
- `packages/core/src/tools/tool-registry.ts`
|
||||
- `packages/core/src/tools/mcp-client-manager.ts`
|
||||
- `packages/core/src/core/client.ts`
|
||||
|
||||
### 2.4 [P1] 启动分析器增强
|
||||
|
||||
**现状**:`packages/cli/src/utils/startupProfiler.ts` 仅记录粗粒度 phase 边界,并且交互式模式下在 UI render 前后 finalize,无法定位 `config.initialize()`、MCP 首工具注册、Gemini tools 刷新的具体瓶颈。
|
||||
|
|
@ -327,6 +436,11 @@ void toolRegistry.discoverMcpToolsProgressively({
|
|||
5. 保存滚动 10 次运行历史到 `~/.qwen/startup-perf/`,支持回归检测
|
||||
6. 在 profile 中标记 `interactive` / `non_interactive`,避免把两种启动路径混合比较
|
||||
|
||||
**补充约束**:
|
||||
|
||||
- `--startup-profile` 若落地,必须透传到 sandbox child process,不能只在父进程消费参数
|
||||
- 非 sandbox/dev 采集模式若开放,需显式避免父子进程双写 profile
|
||||
|
||||
**影响范围**:
|
||||
|
||||
- `packages/cli/src/utils/startupProfiler.ts` — 增强记录能力
|
||||
|
|
@ -351,58 +465,80 @@ void toolRegistry.discoverMcpToolsProgressively({
|
|||
|
||||
**预期收益**:`processUptimeAtT0Ms`(V8 解析时间)减少 20%+。该项与 `03-rendering-extensibility.md` 的代码高亮缓存/预热方案联动实施。
|
||||
|
||||
## 3. 竞品参考
|
||||
## 3. 竞品参考与路线校准
|
||||
|
||||
### Claude Code 启动优化策略
|
||||
|
||||
Claude Code 在 `src/main.tsx` 中实现了激进的并行初始化:
|
||||
### 3.1 Gemini CLI:渐进可用与启动后初始化
|
||||
|
||||
```typescript
|
||||
// MCP 配置提前并行加载
|
||||
const [localMcpPromise, claudeaiMcpPromise] = [
|
||||
loadLocalMcpConfig(),
|
||||
loadClaudeAiMcpConfig(),
|
||||
];
|
||||
// 入口延迟加载交互 UI
|
||||
const { startInteractiveUI } = await import('./interactiveCli.js');
|
||||
|
||||
// 设置/信任对话框与 MCP 连接并行运行
|
||||
Promise.all([ensureMdmSettingsLoaded(), ensureKeychainPrefetchCompleted()]);
|
||||
// mount 后再初始化 config / MCP
|
||||
useEffect(() => {
|
||||
await config.initialize();
|
||||
startupProfiler.flush(config);
|
||||
}, []);
|
||||
|
||||
// MCP 连接使用 Promise.race 超时保护
|
||||
Promise.race([claudeaiConnect, timeout]);
|
||||
|
||||
// 渲染后延迟预取(fire-and-forget)
|
||||
void prefetchAllMcpResources();
|
||||
|
||||
// 特性门控的懒加载
|
||||
const module = feature('FLAG') ? require('./module.js') : null;
|
||||
// UI 用事件化 MCP 状态驱动提示
|
||||
coreEvents.on(CoreEvent.McpClientUpdate, onChange);
|
||||
```
|
||||
|
||||
**关键设计差异**:
|
||||
**对 qwen-code 的启示**:
|
||||
|
||||
- Claude Code 的 MCP 配置加载在 UI 渲染**之前**就开始
|
||||
- 使用 `Promise.race` 而非等待所有 Server
|
||||
- 非关键预取使用 `void` fire-and-forget 模式
|
||||
- 特性门控避免加载不需要的模块
|
||||
- 入口延迟加载可以前移到 P0/P1,而不是等到 bundle 优化阶段
|
||||
- `config.initialize()` 的 render 后执行必须被 profiler 覆盖
|
||||
- MCP 渐进可用需要 UI 明确表达“哪些功能先可用、哪些仍在初始化”
|
||||
|
||||
### 3.2 Claude Code:顶层并行预取与运行期 MCP 生命周期
|
||||
|
||||
Claude Code 在 `src/main.tsx` 和 `src/services/mcp/useManageMCPConnections.ts` 中体现了更激进的策略:
|
||||
|
||||
```typescript
|
||||
// 顶层就启动后台读取,和后续 imports 并行
|
||||
profileCheckpoint('main_tsx_entry');
|
||||
startMdmRawRead();
|
||||
startKeychainPrefetch();
|
||||
|
||||
// 冷路径 feature-gated require
|
||||
const module = feature('FLAG') ? require('./module.js') : null;
|
||||
|
||||
// MCP 更新 16ms 批量刷入
|
||||
const MCP_BATCH_FLUSH_MS = 16;
|
||||
setTimeout(flushPendingUpdates, MCP_BATCH_FLUSH_MS);
|
||||
```
|
||||
|
||||
**对 qwen-code 的启示**:
|
||||
|
||||
- “产物体积优化”不只是分析 bundle,还要主动把冷路径从首屏剥离
|
||||
- MCP 需要设计成运行期持续更新系统,而不只是一次性 startup task
|
||||
- 远端/长生命周期 server 的 reconnect、list-changed 事件、状态批处理应纳入后续阶段设计
|
||||
|
||||
## 4. 实施优先级与里程碑
|
||||
|
||||
| 优先级 | 方案 | 周次 | 风险 | 预期改善 |
|
||||
| ------ | ------------------ | ---- | ---- | -------------------- |
|
||||
| P0 | 启动观测基线 | 1 | 低 | 指标口径可信 |
|
||||
| P0 | 并行 Settings 加载 | 4 | 中 | 配置加载耗时 -30~50% |
|
||||
| P0 | 并行化 UI 前初始化 | 5 | 低 | TTI -200~400ms |
|
||||
| P1 | 渐进式 MCP 可用性 | 6-7 | 中 | 首工具可见 < 2s |
|
||||
| P1 | 启动分析器增强 | 1-2 | 低 | 持续监控能力 |
|
||||
| P2 | 产物体积优化 | 10 | 中 | 冷启动 -20% |
|
||||
| 优先级 | 方案 | 周次 | 风险 | 预期改善 |
|
||||
| ------ | ------------------------ | ---- | ---- | -------------------- |
|
||||
| P0 | 启动观测基线 | 1 | 低 | 指标口径可信 |
|
||||
| P0 | 启动分析器增强 | 1-2 | 低 | 持续监控能力 |
|
||||
| P0 | 入口延迟加载与冷路径裁剪 | 2-3 | 中 | 冷启动解析时间下降 |
|
||||
| P0 | 并行 Settings 加载 | 4 | 中 | 配置加载耗时 -30~50% |
|
||||
| P0 | 并行化 UI 前初始化 | 5 | 低 | TTI -200~400ms |
|
||||
| P1 | 渐进式 MCP 可用性 | 6-7 | 中 | 首工具可见 < 2s |
|
||||
| P2 | 运行期 MCP 生命周期治理 | 8-10 | 中 | 动态变更更稳定 |
|
||||
| P2 | 产物体积优化 | 10 | 中 | 冷启动 -20% |
|
||||
|
||||
## 5. 验证方案
|
||||
|
||||
除本节外,实施前还应对照 `06-implementation-rollout-checklist.md` 中“启动与 MCP 验收清单”的退出标准。
|
||||
|
||||
### 5.1 定量指标
|
||||
|
||||
```bash
|
||||
# 启动 profile 对比
|
||||
QWEN_CODE_PROFILE_STARTUP=1 qwen-code --prompt "test"
|
||||
|
||||
# 当前实现下,这个命令需要确保实际运行在 sandbox child process 中;否则可能不会生成 profile。
|
||||
# 如果后续引入 --startup-profile 或 dev 模式采集,需在文档和工具输出中明确标识采集路径。
|
||||
|
||||
# 重点关注指标:
|
||||
# - processUptimeAtT0Ms: V8 模块解析时间
|
||||
# - after_load_settings: 配置加载完成时间
|
||||
|
|
@ -426,6 +562,8 @@ QWEN_CODE_PROFILE_STARTUP=1 qwen-code --prompt "test"
|
|||
| 网络不可用 | 超时后优雅降级,显示警告 |
|
||||
| 冷启动 vs 热启动 | 两种场景均有改善 |
|
||||
| 正在进行的模型请求中 MCP 工具变化 | 当前请求工具集合不变,下一次请求看到更新 |
|
||||
| 运行期 MCP 工具/资源变更 | UI 状态批量刷新,不出现工具列表抖动或重复 setTools |
|
||||
| 非交互命令 / 测试 harness | 入口延迟加载不改变非交互路径行为 |
|
||||
|
||||
### 5.3 向后兼容
|
||||
|
||||
|
|
@ -433,3 +571,4 @@ QWEN_CODE_PROFILE_STARTUP=1 qwen-code --prompt "test"
|
|||
- MCP discovery 超时降低需提供配置项允许用户恢复长超时;tool call 超时不随 discovery 默认值改变
|
||||
- 渐进式工具注册需确保不破坏现有的工具描述生成逻辑,并通过 `GeminiClient.setTools()` debounce 刷新
|
||||
- 不直接使用会全局清空 discovered tools/prompts 的 `ToolRegistry.discoverMcpTools()` 作为后台渐进入口
|
||||
- 入口延迟加载不能改变非交互路径、测试 harness 和 CLI 子命令的模块求值顺序
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue