chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,231 @@
# 首消息性能优化报告
**日期**: 2026-03-23
**优化目标**: 降低首消息冷启动Agent 响应延迟
**优化前**: ~1662ms
**优化后**: ~349ms
**提升幅度**: **79%**
---
## 1. 问题分析
### 1.1 原始性能数据
首消息 `ensureEngineForRequest` 耗时分解:
```
ensureEngine 总耗时: 1662ms
├── parseCtxServers: 5ms
├── syncMcp: 1315ms ← 主要瓶颈79%
└── getOrCreate: 342ms
├── memory: 231ms
└── init: 102ms
```
### 1.2 瓶颈定位
| 阶段 | 耗时 | 占比 | 说明 |
|------|------|------|------|
| `syncMcpConfigToProxyAndReload` | 1315ms | 79% | MCP 代理同步,首消息必须等待 |
| `ensureMemoryReadyForSession` | 231ms | 14% | Memory 服务初始化 |
| `engine.init` | 102ms | 6% | ACP 引擎进程启动 |
| 其他 | 14ms | 1% | 配置解析、MCP 提取等 |
---
## 2. 优化方案
### 2.1 优化一MCP Proxy Bridge 预热
**原理**: 在服务 `init()` 时后台预热 MCP proxy bridge避免首消息同步阻塞。
**代码位置**: `src/main/services/engines/unifiedAgent.ts`
```typescript
/**
* 🚀 性能优化:后台预热 MCP proxy bridge
* 在 init() 时调用,避免首包 syncMcp 阻塞 ~1.3s
*/
private warmupMcpBridge(): void {
(async () => {
try {
const { syncMcpConfigToProxyAndReload } = await import("../packages/mcp");
// 使用空配置预热(仅启动默认服务如 chrome-devtools
await syncMcpConfigToProxyAndReload({});
log.info("[UnifiedAgent] ✅ MCP proxy bridge 预热完成");
} catch (err) {
log.warn("[UnifiedAgent] MCP proxy bridge 预热失败:", err);
}
})().catch(() => {});
}
```
**调用时机**: 在 `init()` 方法末尾调用与其他预热任务Engine 预热、Memory 预热)并行执行。
### 2.2 优化二syncMcp 与 ensureMemoryReady 并行执行
**原理**: 当 MCP 配置变更且需要创建新引擎时,将 `syncMcp``ensureMemoryReady` 并行执行。
**代码位置**: `src/main/services/engines/unifiedAgent.ts``ensureEngineForRequest()`
```typescript
// 🚀 性能优化:只有在 MCP 配置变更时才调用 syncMcpConfigToProxyAndReload
// 同时,如果需要创建新引擎(无现有引擎),并行执行 ensureMemoryReady
const needCreateEngine = !existingEngine || !existingEngine.isReady;
let memoryReadyPromise: Promise<void> | null = null;
if (mcpChanged) {
const syncStart = Date.now();
try {
const { syncMcpConfigToProxyAndReload } = await import("../packages/mcp");
const syncPromise = syncMcpConfigToProxyAndReload(requestMcpServersEarly);
// 🚀 性能优化:如果需要创建新引擎,并行执行 ensureMemoryReady
if (needCreateEngine && memoryService.isInitialized()) {
memoryReadyPromise = memoryService.ensureMemoryReadyForSession()
.then(() => {
log.debug(`⏱️ [PERF] ensureMemoryReady 并行完成: ${Date.now() - syncStart}ms`);
})
.catch((err) => {
log.warn("[UnifiedAgent] Memory sync check failed:", err);
});
}
await syncPromise;
} catch (e) {
log.warn("[UnifiedAgent] 动态同步 MCP 配置到 proxy 失败:", e);
}
}
```
**`getOrCreateEngine` 方法修改**:
```typescript
async getOrCreateEngine(
projectId: string,
effectiveConfig: AgentConfig,
memoryReadyPromise?: Promise<void> | null, // 新增参数
): Promise<AcpEngine> {
// ...
// 🚀 性能优化:如果已有并行的 memoryReadyPromise直接等待它
if (memoryReadyPromise) {
await memoryReadyPromise;
} else if (memoryService.isInitialized()) {
await memoryService.ensureMemoryReadyForSession();
}
// ...
}
```
### 2.3 已有优化(复用)
以下优化在之前已实现,本次优化复用:
1. **快速路径 1**: 无配置变更 → 直接返回现有引擎7ms
2. **快速路径 2**: MCP 未变更 → 跳过 syncMcp
3. **Engine 预热池**: 预热 claude-code 和 qimingcode 引擎
4. **Memory 预热**: `init()` 时后台执行 `ensureMemoryReadyForSession()`
---
## 3. 测试对照
### 3.1 测试环境
- **应用版本**: 0.9.1
- **测试时间**: 2026-03-23 17:53
- **测试场景**: 重启应用后发送首条消息
### 3.2 优化前日志2026-03-20
```
ensureEngine 总耗时: 1662ms
├── parseCtxServers: 5ms
├── syncMcp: 1315ms
└── getOrCreate: 342ms
├── memory: 231ms
└── init: 102ms
```
### 3.3 优化后日志2026-03-23
```
[17:52:55] [info] 🔥 后台预热 Engine: claude-code
[17:52:55] [info] 🔥 后台预热 Engine: qimingcode
[17:52:55] [info] ✅ 预热 Engine 就绪,等待复用: claude-code
[17:52:56] [info] ✅ MCP proxy bridge 预热完成
[17:52:58] [info] ✅ 预热 Engine 就绪,等待复用: qimingcode
[17:53:34] 首消息请求到达
[17:53:34] [debug] ⏱️ [ensureEngine][PERF] 解析 context_servers 耗时: 6ms
[17:53:34] [info] ⏱️ [PERF] 走完整路径: 无现有引擎, MCP变更(新7个)
[17:53:34] [debug] ⏱️ [PERF] ensureMemoryReady 并行完成: 2ms
[17:53:34] [info] ⏱️ [PERF] syncMcp 完成: 耗时 2ms | 并行memory=true
[17:53:34] [debug] ⏱️ [ensureEngine][PERF] 提取 real MCP servers 耗时: 0ms
[17:53:34] [debug] ⏱️ [ensureEngine][PERF] ensureBridgeStarted(有MCP) 耗时: 0ms
[17:53:34] [debug] ⏱️ [getOrCreateEngine][PERF] 等待并行 ensureMemoryReady 完成: 231ms
[17:53:34] [debug] ⏱️ [getOrCreateEngine][PERF] engine.init 耗时: 105ms
[17:53:34] [info] ⏱️ [getOrCreateEngine][PERF] 总耗时: 336ms | 使用并行memory=true
[17:53:34] [info] ⏱️ [ensureEngine][PERF] 总耗时: 349ms | 并行优化=true
[17:53:34] [info] ⏱️ [HTTP][PERF] /computer/chat 总耗时: 362ms
```
### 3.4 性能对比表
| 指标 | 优化前 | 优化后 | 提升幅度 |
|------|--------|--------|----------|
| `syncMcp` | 1315ms | 2ms | **99.8%** ↓ |
| `ensureEngine` 总耗时 | 1662ms | 349ms | **79%** ↓ |
| `/computer/chat` 总耗时 | ~1700ms | 362ms | **79%** ↓ |
### 3.5 优化后耗时分解
```
ensureEngine 总耗时: 349ms
├── parseCtxServers: 6ms
├── syncMcp: 2ms ← 预热后从 1315ms 降至 2ms
├── extractReal: 0ms
├── ensureBridge: 0ms
└── getOrCreate: 341ms
├── memory: 231ms ← 与 syncMcp 并行执行
└── init: 105ms
```
---
## 4. 优化效果总结
### 4.1 关键收益
1. **MCP Proxy Bridge 预热**: 消除首消息 ~1.3s 阻塞
2. **并行执行**: Memory 准备与 MCP 同步重叠执行
3. **Engine 预热池**: 复用已启动的引擎进程
### 4.2 用户体验改善
| 场景 | 优化前 | 优化后 |
|------|--------|--------|
| 首消息响应 | ~1.7s | ~0.35s |
| 用户感知 | 明显延迟 | 接近即时 |
### 4.3 注意事项
1. **预热时机**: 所有预热任务在 `init()` 末尾启动,不阻塞应用启动
2. **内存占用**: 预热引擎会占用额外内存,但通过预热池容量限制(当前 1 个/类型)控制
3. **配置变更**: 如果首消息的 MCP 配置与预热配置差异较大,仍需同步,但比冷启动快
---
## 5. 相关文件
- `src/main/services/engines/unifiedAgent.ts` - 主优化文件
- `src/main/services/packages/mcp.ts` - MCP proxy 管理
- `src/main/services/packages/mcpHelpers.ts` - MCP 配置比较工具
## 6. 参考资料
- [ACP Engine 性能优化](./architecture/ACP-ENGINE-PERF-OPTIMIZATION.md)
- [深度性能研究报告](./DEEP-RESEARCH-PERF-REPORT-2026-03-20.md)

View File

@@ -0,0 +1,121 @@
# macOS 平台首消息性能测试报告(重启后新会话)
**报告日期**: 2026-03-24
**测试平台**: macOS
**引擎类型**: claude-code
**日志来源**: `/Users/apple/.qimingclaw/logs/perf.2026-03-24.log`
**参考模板**: `docs/optimization/WINDOWS-FIRST-MESSAGE-PERF-REPORT-2026-03-23.md`
---
## 执行摘要
本报告针对“重启客户端后首个新会话”进行性能核对。前端显示为 **00:37**,后端 PERF 日志显示两段关键新会话数据:
1. **16:04 会话**:仅记录到 `sse.firstChunk`,缺少 `sse.end`,无法直接计算完整总时长。
2. **16:07 会话**:链路完整,可核算端到端耗时约 **30.163s**`create-workspace -> sse.end`)。
### 关键发现
| 指标 | 16:04 首会话(重启后) | 16:07 完整会话 | 说明 |
|-----|------------------|---------------|------|
| create-workspace | 2730ms | 275ms | 16:07 工作区阶段明显更快(可能复用) |
| /chat 总耗时 | 148ms | 139ms | API 前置开销很小 |
| ensureEngine | 127ms | 121ms | 引擎准备并非瓶颈 |
| sse 首包延迟connect->firstChunk | 10.999s | 12.555s | 首包等待明显 |
| sse streaming | 缺失 | 17.333s | 仅 16:07 可核算 |
| 端到端create-workspace->sse.end | 缺失 | 30.163s | 前端 00:37 高于该值 |
---
## 一、测试会话数据
### 1.1 会话 A重启后首个新会话日志不完整
| 时间 | session_id | create-workspace | /chat | ensureEngine | sse.connect | sse.firstChunk | sse.end |
|-----|-----------|------------------|-------|--------------|-------------|----------------|---------|
| 16:04:54 | `e418fa45-a2a5-4a77-8830-abe3449cbc9c` | 2730ms | 148ms | 127ms | 16:04:54.608 | 16:05:05.607 | 缺失 |
**可计算片段**:
- `create-workspace -> sse.firstChunk` = `16:04:54.256 -> 16:05:05.607` = **11.351s**
- `sse.connect -> sse.firstChunk` = **10.999s**
> 由于缺少 `sse.end`,无法直接验证该次会话是否对应前端显示的 00:37。
### 1.2 会话 B后续完整新会话
| 时间 | session_id | create-workspace | /chat | ensureEngine | sse.connect | sse.firstChunk | sse.end |
|-----|-----------|------------------|-------|--------------|-------------|----------------|---------|
| 16:07:19 | `13711f68-795e-4517-ab4c-f0284ddc3070` | 275ms | 139ms | 121ms | 16:07:20.000 | 16:07:32.555 | 16:07:49.888 |
**详细分解**:
- `create-workspace -> /chat` = `16:07:19.697 -> 16:07:19.962` = **0.265s**
- `sse.connect -> firstChunk` = **12.555s**
- `sse streaming` = **17.333s**(日志直接给出)
- `create-workspace -> sse.end` = `16:07:19.697 -> 16:07:49.888` = **30.163s**
---
## 二、性能分析
### 2.1 时间结构(基于完整会话 B
```text
总耗时create-workspace -> sse.end: 30.163s
├── 前置阶段workspace + chat + engine: ~0.265s<1%
├── 首包等待sse.connect -> firstChunk: 12.555s41.6%
└── 流式输出firstChunk -> end: 17.333s57.5%
```
结论:
- 当前主要耗时不在 `ensureEngine`(约 0.12s),而在 **首包等待 + 流式输出阶段**
- 与“历史首消息慢主要在引擎准备”的模式不同,本次瓶颈更偏向模型响应阶段。
### 2.2 与前端 00:37 的差异
- 日志可完整核算值:**30.163s**(会话 B
- 前端显示值:**37s**。
- 差异约:**6.8s**。
可能原因(按优先级):
1. 前端计时起点早于 `create-workspace`(例如点击发送或会话初始化前的 UI 阶段)。
2. 16:04 首会话虽然日志缺 `sse.end`,但前端仍完成并纳入了完整计时。
3. 前端停止计时点晚于 `sse.end`(渲染收尾、状态同步后才停止)。
---
## 三、结论与建议
### 3.1 结论
1. 重启后首个新会话16:04链路不完整无法直接从后端日志确认 37s。
2. 后续完整会话16:07总耗时约 **30.163s**,量级接近但低于前端 37s。
3. 当前瓶颈集中在 **SSE 首包与流式阶段**`ensureEngine` 非主要问题。
### 3.2 建议
1. 统一前后端计时口径:明确起点/终点并关联同一 `rid/sessionId`
2. 增加 `sse.end` 丢失兜底埋点(取消、断流、窗口关闭等路径也记录结束事件)。
3. 增补前端埋点字段:`frontend_timer_start_ts``frontend_timer_end_ts`,与后端 PERF 同屏输出便于核对。
---
## 附录:原始日志摘录
```text
[2026-03-24 16:04:54.256] [info] [PERF] create-workspace: 2730ms rid=[283ou9vg2]
[2026-03-24 16:04:54.569] [info] [PERF] /chat: 148ms rid=68d27799 (parseBody=8ms validate=1ms workspace=1ms engine=127ms chat=11ms)
[2026-03-24 16:04:54.608] [info] [PERF] sse.connect session=e418fa45-a2a5-4a77-8830-abe3449cbc9c
[2026-03-24 16:05:05.607] [info] [PERF] sse.firstChunk session=e418fa45-a2a5-4a77-8830-abe3449cbc9c
[2026-03-24 16:07:19.697] [info] [PERF] create-workspace: 275ms rid=[llrt9utmb]
[2026-03-24 16:07:19.962] [info] [PERF] /chat: 139ms rid=1107a1f7 (parseBody=7ms validate=1ms workspace=0ms engine=121ms chat=10ms)
[2026-03-24 16:07:20.000] [info] [PERF] sse.connect session=13711f68-795e-4517-ab4c-f0284ddc3070
[2026-03-24 16:07:32.555] [info] [PERF] sse.firstChunk session=13711f68-795e-4517-ab4c-f0284ddc3070
[2026-03-24 16:07:49.888] [info] [PERF] sse.end: 17333ms streaming session=13711f68-795e-4517-ab4c-f0284ddc3070
```
---
*报告生成时间: 2026-03-24*
*备注: 本报告为 macOS 单平台分析,重点核对前端 00:37 与后端 PERF 的时间差。*

View File

@@ -0,0 +1,138 @@
# macOS 平台首消息性能测试报告(连续新会话)
**报告日期**: 2026-03-31
**测试平台**: macOS
**引擎类型**: qimingcode
**日志来源**: `/Users/apple/.qimingclaw/logs/perf.2026-03-31.log`00:17:30 ~ 00:20:39
**参考模板**: `docs/optimization/MACOS-FIRST-MESSAGE-PERF-REPORT-2026-03-24.md`
---
## 执行摘要
本报告针对“连续新开会话”的**首条消息首次响应性能**进行核对,口径为:
- 主要口径:`create-workspace -> sse.firstToken`
- 辅助口径:`/chat received -> 首 token``/chat` + `sendToFirstUpdate`
本轮共观测到 6 次新会话尝试:
1. **5 次完整会话**(均包含 `sse.firstToken`
2. **1 次中断会话**(仅 `create-workspace`,未进入 `/chat`
### 关键发现
| 指标 | 本轮结果5 次完整会话) | 说明 |
|-----|------------------|------|
| 首次响应create-workspace -> firstToken | **平均 9.307s**8.063s ~ 10.928s | 用户体感“首字出来”耗时 |
| `/chat` 总耗时 | **平均 2.955s**2.060s ~ 3.697s | 前置阶段 |
| `sendToFirstUpdate` | **平均 6.304s**5.885s ~ 7.699s | 首 token 等待阶段,主瓶颈 |
| `/chat``ensureEngine` | **平均 2.042s**1.367s ~ 3.011s | A 段内部主耗时 |
| 稳定性 | 5/5 完整会话成功 | 本时间窗内未见 `newSession/chat` 报错 |
---
## 一、测试会话数据
### 1.1 五次完整新会话(首消息)
| 会话 | project_id | rid | session_id | create-workspace | `/chat` | ensureEngine | sessionSetup | sse.connect | sse.firstToken | 首次响应create->firstToken |
|-----|------------|-----|------------|------------------|---------|--------------|--------------|-------------|----------------|-------------------------------|
| S1 | 1564329 | `0d3f44ba` | `ses_2c076bcb0ffeNfAHi2Yiu6tpru` | 00:17:30.613 | 3169ms | 1672ms | 1495ms | 00:17:33.851 | 00:17:39.723 | **9.110s** |
| S2 | 1564331 | `b2d8d26f` | `ses_2c075ef03ffeMiHTQKKR4h0FkZ` | 00:18:23.562 | 2652ms | 1982ms | 669ms | 00:18:26.284 | 00:18:32.234 | **8.672s** |
| S3 | 1564332 | `1b4b5eeb` | `ses_2c07552afffeFIQODrEZMYpydC` | 00:19:02.567 | 3697ms | 3011ms | 683ms | 00:19:06.309 | 00:19:12.328 | **9.761s** |
| S4 | 1564333 | `c00c4d52` | `ses_2c074e299ffe6qfg8LTgbN2lET` | 00:19:32.857 | 2060ms | 1367ms | 692ms | 00:19:35.003 | 00:19:40.920 | **8.063s** |
| S5 | 1564334 | `0d815846` | `ses_2c0742965ffebmYiW5e8JIydTk` | 00:20:19.169 | 3199ms | 2179ms | 1019ms | 00:20:22.411 | 00:20:30.097 | **10.928s** |
### 1.2 中断会话(仅创建工作区)
| 时间 | rid | 事件 |
|-----|-----|------|
| 00:18:08.206 | `5mia20hej` | 仅 `create-workspace`,未进入 `/chat`,不纳入首次响应统计 |
---
## 二、性能分析(首消息首次响应口径)
### 2.1 首次响应时间结构(均值)
```text
平均首次响应create-workspace -> firstToken: 9.307s
├── create-workspace -> /chat received: ~0.047s0.5%
├── A: /chat received -> /chat 返回: 2.955s31.8%
└── B: /chat 返回 -> firstToken: 6.304s67.7%
```
结论:
- 若目标是“新会话首条消息尽快出首字”,**B 段是主瓶颈**(约 2/3
- A 段次之;其中仍以 `ensureEngine` 为主,但总体占端到端首响仅约 22%。
### 2.2 A 段内部(/chat 阶段)分解
| 指标 | 平均耗时 | 占 A 段 |
|-----|---------|--------|
| ensureEngine | 2042ms | 69.1% |
| sessionSetup/chat 其余 | 912ms | 30.9% |
说明:
- A 段仍有优化空间,但不是首响的第一优先级。
- 本轮 A 段波动2.060s~3.697s)主要来自 `ensureEngine``sessionSetup` 抖动。
### 2.3 抖动观察
| 阶段 | 最小 | 最大 | 波动特征 |
|-----|------|------|---------|
| `/chat` | 2060ms | 3697ms | 中等波动 |
| `sendToFirstUpdate` | 5885ms | 7699ms | 中等偏高S5 明显偏慢 |
| `firstToken->end_turn` | 8008ms | 25402ms | 该段波动极大(与“首响”无关,但影响整体完成时间) |
---
## 三、结论与建议
### 3.1 结论
1. 本轮“新会话首条消息首次响应”平均约 **9.3s**
2. 主要瓶颈不在 `/chat` 前置,而在 **`/chat` 返回后等待首 tokenB 段)**。
3. 本时间窗内 5 次完整会话均成功,未出现上一轮的 `newSession` 内部错误。
### 3.2 建议(按首响收益排序)
1. 先优化 `sendToFirstUpdate`(目标先压到 <5s再冲 <4s
2. 再收敛 `ensureEngine` 抖动(优先把 3s 档位压回 2s 内)。
3. 保留当前 `sse.firstToken` 埋点,后续把优化验收口径统一为 `create-workspace -> sse.firstToken`
---
## 附录:关键日志摘录
```text
[2026-03-31 00:17:30.613] [PERF] create-workspace: 66ms rid=[j197gc94h]
[2026-03-31 00:17:33.838] [PERF] /chat: 3169ms rid=0d3f44ba (parseBody=0ms validate=1ms workspace=0ms engine=1672ms chat=1496ms)
[2026-03-31 00:17:39.723] [PERF] sse.firstToken session=ses_2c076bcb0ffeNfAHi2Yiu6tpru
[2026-03-31 00:17:39.723] [PERF] acp.prompt.sendToFirstUpdate: 5885ms sessionId=ses_2c076bcb0ffeNfAHi2Yiu6tpru
[2026-03-31 00:18:23.562] [PERF] create-workspace: 49ms rid=[fg7ng07b6]
[2026-03-31 00:18:26.269] [PERF] /chat: 2652ms rid=b2d8d26f (parseBody=0ms validate=1ms workspace=0ms engine=1982ms chat=669ms)
[2026-03-31 00:18:32.234] [PERF] sse.firstToken session=ses_2c075ef03ffeMiHTQKKR4h0FkZ
[2026-03-31 00:18:32.234] [PERF] acp.prompt.sendToFirstUpdate: 5965ms sessionId=ses_2c075ef03ffeMiHTQKKR4h0FkZ
[2026-03-31 00:19:02.567] [PERF] create-workspace: 51ms rid=[fy8f9owgd]
[2026-03-31 00:19:06.296] [PERF] /chat: 3697ms rid=1b4b5eeb (parseBody=1ms validate=0ms workspace=1ms engine=3011ms chat=684ms)
[2026-03-31 00:19:12.328] [PERF] sse.firstToken session=ses_2c07552afffeFIQODrEZMYpydC
[2026-03-31 00:19:12.328] [PERF] acp.prompt.sendToFirstUpdate: 6033ms sessionId=ses_2c07552afffeFIQODrEZMYpydC
[2026-03-31 00:19:32.857] [PERF] create-workspace: 72ms rid=[2kfaik1wz]
[2026-03-31 00:19:34.980] [PERF] /chat: 2060ms rid=c00c4d52 (parseBody=0ms validate=0ms workspace=1ms engine=1367ms chat=692ms)
[2026-03-31 00:19:40.920] [PERF] sse.firstToken session=ses_2c074e299ffe6qfg8LTgbN2lET
[2026-03-31 00:19:40.920] [PERF] acp.prompt.sendToFirstUpdate: 5940ms sessionId=ses_2c074e299ffe6qfg8LTgbN2lET
[2026-03-31 00:20:19.169] [PERF] create-workspace: 46ms rid=[e6dtwydgo]
[2026-03-31 00:20:22.398] [PERF] /chat: 3199ms rid=0d815846 (parseBody=0ms validate=1ms workspace=0ms engine=2179ms chat=1019ms)
[2026-03-31 00:20:30.097] [PERF] sse.firstToken session=ses_2c0742965ffebmYiW5e8JIydTk
[2026-03-31 00:20:30.097] [PERF] acp.prompt.sendToFirstUpdate: 7699ms sessionId=ses_2c0742965ffebmYiW5e8JIydTk
```
---
*报告生成时间: 2026-03-31*
*备注: 本报告仅针对“新会话首条消息首次响应first token”口径不包含完整回答结束耗时优化判断。*

View File

@@ -0,0 +1,69 @@
# Session Reuse Fix - 2026-03-24
## Problem
连续会话(同一个 project每次消息都需要重新创建 engine导致约 3-7 秒的额外延迟。
## Root Cause
`detectConfigChange` 比较环境变量时,`OPENCODE_LOG_DIR` 的值不一致:
- `engineConfigs` 存储的是**本地化后的路径**: `/Users/apple/.qimingclaw/logs`
- `resolvedEnv` 来自请求,包含**容器路径**: `/app/container-logs`
即使两个路径在功能上等价(容器路径会被本地化),字符串比较仍然返回不同,导致 `envChanged: true`,触发 engine 重建。
## Solution
`detectConfigChange` 方法中添加与 `ensureEngineForRequest` 相同的本地化逻辑:
```typescript
// unifiedAgent.ts lines 927-938
let normalizedResolvedEnv = resolvedEnv;
if (
resolvedEnv?.OPENCODE_LOG_DIR &&
!fs.existsSync(resolvedEnv.OPENCODE_LOG_DIR)
) {
normalizedResolvedEnv = {
...resolvedEnv,
OPENCODE_LOG_DIR: path.join(os.homedir(), APP_DATA_DIR_NAME, "logs"),
};
}
```
然后用 `normalizedResolvedEnv` 进行比较,而非原始的 `resolvedEnv`
## Performance Impact
### Before Fix
| Request | Engine Time | Path |
|---------|-------------|------|
| 1st | 3797ms | fullPath (create) |
| 2nd | 3063ms | fullPath (recreate - config changed) |
| 3rd | 6028ms | fullPath (recreate - config changed) |
### After Fix
| Request | Engine Time | Path |
|---------|-------------|------|
| 1st | 2894ms | fullPath (create) |
| 2nd | 5ms | fastPath (reuse) |
| 3rd+ | ~5ms | fastPath (reuse) |
**Improvement**: 连续消息的 engine 准备时间从 3-7 秒降至 ~5ms99%+ 减少)
## Files Changed
- `src/main/services/engines/unifiedAgent.ts`: 添加 `OPENCODE_LOG_DIR` 本地化到 `detectConfigChange` 方法
## Test Verification
1. 发送第一条消息到新 project → 观察 `engine.getOrCreate: ~3000ms`
2. 发送第二条消息到同一 project → 观察 `engine.ensure: ~5ms`
3. 日志应显示 `detectConfigChange: unchanged` 或无 CHANGED 日志
## Related
- `ensureEngineForRequest` 中的本地化逻辑 (lines 777-787)
- `detectConfigChange` 方法 (lines 912-1030)

View File

@@ -0,0 +1,194 @@
# Warmup Runtime Config 缓存优化
**日期**: 2026-03-31
**类型**: 性能优化
**影响范围**: 新会话首消息响应(连续创建新会话场景)
---
## 1. 问题背景
### 1.1 现象
连续新开会话时,前 1-2 个会话的 warmup 复用始终 miss日志出现
```
[EngineWarmup] ⚠️ 运行时配置不兼容,不复用 warmupmodel,apiKey,baseUrl,apiProtocol
warmupModel: '(none)',
warmupBaseUrl: '(none)',
warmupApiKeySet: false,
warmupApiProtocol: '(none)',
```
### 1.2 实测数据20:52 ~ 20:565 个连续新会话)
| # | 时间 | Warmup | Init(ms) | TTFT(ms) | 总响应(ms) |
|---|------|--------|----------|----------|-----------|
| 1 | 20:52 | ❌ coldruntime 不兼容) | 5,705 | 9,507 | 10,389 |
| 2 | 20:53 | ❌ coldruntime 不兼容) | 6,097 | 6,236 | 14,107 |
| 3 | 20:54:15 | ✅ reuse | 1,513 | 6,089 | 20,938 |
| 4 | 20:54:50 | ✅ reuse | 1,523 | 5,983 | 20,510 |
| 5 | 20:55:20 | ✅ reuse | 1,701 | 5,964 | 39,269 |
前两个会话 warmup miss 导致额外 ~2-4s 冷启动开销。
### 1.3 根因
**配置构建时机差异**
```
init() 阶段:
baseConfig = { workspaceDir, engine, env }
↓ 缺少 model/apiKey/baseUrl/apiProtocol
chat() 阶段:
effectiveConfig = {
...baseConfig,
model: mp?.model, ← 来自 model_provider
apiKey: mp?.api_key, ← 来自 model_provider
baseUrl: mp?.base_url, ← 来自 model_provider
apiProtocol: mp?.api_protocol, ← 来自 model_provider
mcpServers: freshMcpServers,
}
```
- `baseConfig``init()` 时设置,不含运行时配置
- `effectiveConfig` 在每次 `chat()` 请求时动态构建
- `warmup.start(baseConfig)` 用的是不含 runtime config 的 baseConfig
- `tryReuse()` 检测到 runtime config 不一致 → 回退冷启动
**影响的三个调用路径**
| 调用点 | 触发场景 | seedConfig | 结果 |
|--------|---------|-----------|------|
| `warmup.start(this.baseConfig)` | `init()` | ❌ 无 | warmup 缺 runtime config |
| `warmup.respawn(this.baseConfig)` | 会话结束 | ❌ 无 | 同上 |
| `ensureQimingWarmup({reason: "get_or_create_guard"})` | 引擎不存在 | ❌ 无 | 同上 |
| `ensureQimingWarmup({seedConfig: {...}})` | warmup 被消费后 refill | ✅ 有 | 正常 |
| `ensureQimingWarmup({seedConfig: {...}})` | 冷启动后 refill | ✅ 有 | 正常 |
**结论**:只有 refill 路径能命中init / respawn / guard 三个路径永远 miss。
---
## 2. 修复方案
### 2.1 核心思路
`EngineWarmup` 内部缓存最近一次请求的 runtime config`model/apiKey/baseUrl/apiProtocol`),在 `start()` 创建 warmup 时自动应用缓存值。
### 2.2 改动清单
| 文件 | 改动 |
|------|------|
| `engineWarmup.ts` | 新增 `lastRuntimeConfig` 缓存 + `cacheRuntimeConfig()` 方法 |
| `unifiedAgent.test.ts` | 新增 2 个测试场景 |
### 2.3 具体改动
#### `engineWarmup.ts`
**新增字段**
```typescript
private lastRuntimeConfig: Pick<
AgentConfig,
"model" | "apiKey" | "baseUrl" | "apiProtocol"
> | null = null;
```
**`start()` 应用缓存**
```typescript
const warmupConfig: AgentConfig = {
...baseConfig,
engine: WARMUP_ENGINE_TYPE,
...(this.lastRuntimeConfig
? {
model: this.lastRuntimeConfig.model || baseConfig.model,
apiKey: this.lastRuntimeConfig.apiKey || baseConfig.apiKey,
baseUrl: this.lastRuntimeConfig.baseUrl || baseConfig.baseUrl,
apiProtocol: this.lastRuntimeConfig.apiProtocol || baseConfig.apiProtocol,
}
: {}),
// ...
};
```
**`tryReuse()` 三条路径均缓存**
- Runtime mismatch → `cacheRuntimeConfig(effectiveConfig)` → 后续 refill 用新缓存
- Warmup hit → `cacheRuntimeConfig(effectiveConfig)` → 保持缓存新鲜
- Warmup not ready → `cacheRuntimeConfig(effectiveConfig)` → 同上
**`dispose()` 清理**
```typescript
this.lastRuntimeConfig = null;
```
---
## 3. 配置变更时的行为
缓存的 runtime config 与新请求配置不一致时:
```
warmup (缓存 model=A, key=X)
新请求 (model=B, key=Y)
tryReuse() 检测不兼容 → 回退冷启动 (~2s)
cacheRuntimeConfig(B, Y) → 更新缓存
refill warmup 用 (B, Y) 创建
后续请求 (B, Y) → 命中 ✅
```
**最多影响一次会话**(配置变更的那次),后续自动纠正。这与修改前行为一致——修改前是每次都 miss修改后仅配置变更时 miss 一次。
---
## 4. 预期效果
| 场景 | 修改前 | 修改后 |
|------|--------|--------|
| 首次 init → 首次会话 | miss → 冷启动 ~3.7s | miss → 冷启动 ~3.7s(首次无缓存) |
| 首次会话后 → 第二次会话 | **miss** → 冷启动 ~2.6s | **命中** → ~1.5s(省 ~1.1s |
| 连续新会话(配置不变) | 每次前 1-2 个 miss | 仅首次 miss后续均命中 |
| 配置变更 | miss → 冷启动 | miss → 冷启动 → 自动纠正缓存 |
**关键指标改善**
- 连续新会话 Init 耗时5,705ms / 6,097ms → **~1,500ms**reduced ~70%
- 首消息端到端(不含 LLM TTFT~8s → **~6s**(节省 warmup 冷启动开销)
---
## 5. 测试覆盖
### 5.1 新增测试
| 测试 | 验证内容 |
|------|---------|
| `warmup runtime config 缓存:首次 miss 后 refill 命中后续请求` | 首次 miss → 缓存 → refill 带缓存 → 第二次命中 |
| `warmup runtime config 缓存:配置变更时回退冷启动并更新缓存` | configA miss → refill(A) → configB miss → refill(B) → B 命中 |
### 5.2 回归测试
全部 54 个 warmup 相关测试通过,包括:
- 运行时配置缺失回退冷启动(原有测试不受影响)
- MCP 配置不兼容回退冷启动
- qimingcode 连续命中补仓
- destroy() 清理 warmup
- persistent MCP 归一化
---
## 6. 相关文档
- [首消息性能优化报告 (2026-03-23)](./FIRST-MESSAGE-PERF-OPT-2026-03-23.md)
- [macOS 首消息性能测试报告 (2026-03-31)](./MACOS-FIRST-MESSAGE-PERF-REPORT-2026-03-31.md)
- [Session 复用修复 (2026-03-24)](./SESSION-REUSE-FIX-2026-03-24.md)
---
*文档生成时间: 2026-03-31*

View File

@@ -0,0 +1,195 @@
# Windows 平台首消息性能测试报告
**报告日期**: 2026-03-23
**测试平台**: Windows
**引擎类型**: claude-code
**日志来源**: `C:\Users\soddygo\.qimingclaw\logs\main.2026-03-23.log`
**参考报告**: [DEEP-RESEARCH-PERF-REPORT-2026-03-20.md](../DEEP-RESEARCH-PERF-REPORT-2026-03-20.md) (macOS 测试)
---
## 执行摘要
本报告对比分析了 Windows 平台上首消息性能优化前后的差异,并与 macOS 平台历史数据进行对比。
### 关键发现
| 指标 | 优化前 (cdb41fd) | 优化后 (12a102b) | 提升 |
|-----|-----------------|-----------------|------|
| **首消息总耗时** | ~9,000ms | ~4,000ms | **55%** |
| **ensureEngine** | 8,948ms | 4,018ms | **55%** |
| **引擎启动** | 7,000ms+ | ~3,800ms | **~46%** |
### 与 macOS 对比
| 平台 | 首消息总耗时 | ensureEngine | 状态 |
|-----|------------|--------------|------|
| **macOS** (历史) | ~7,800ms | ~7,700ms | 基准 |
| **Windows 优化前** | ~9,000ms | ~8,900ms | 慢 15% |
| **Windows 优化后** | ~4,000ms | ~4,000ms | **快 48%** |
---
## 一、测试会话数据
### 1.1 优化前会话 (cdb41fd)
| 时间 | session_id | 总耗时 | parseBody | validate | ensureWorkspace | ensureEngine | chat |
|-----|-----------|--------|-----------|----------|-----------------|--------------|------|
| 20:17:22.321 | 526b1884-344f-496d-b948-e143db6b8ac3 | **9,017ms** | 1ms | 6ms | 3ms | **8,948ms** | 59ms |
**详细分解**:
```
ensureEngine 8,948ms 分解:
├── parseCCtxServers: 1,732ms (19%)
├── syncMcp: 5,204ms (58%) ← 最大瓶颈
├── extractReal: 1ms (<1%)
├── ensureBridge: 2ms (<1%)
└── getOrCreate: 2,006ms (22%)
├── mmemory: 263ms
├── evict: 1ms
└── init: 1,737ms
```
### 1.2 优化后会话 (12a102b)
| 时间 | session_id | 总耗时 | parseBody | validate | ensureWorkspace | ensureEngine | chat |
|-----|-----------|--------|-----------|----------|-----------------|--------------|------|
| 19:58:06.135 | a52a4e75-08f5-4f9b-9f73-e49f4f7eb41c | **4,054ms** | 1ms | 3ms | 2ms | **4,046ms** | 3ms |
| 19:58:52.938 | 8cd0f9d9-6c2e-419b-a4f1-7c6ea7559b0c | **3,854ms** | 1ms | 3ms | 2ms | **3,801ms** | 47ms |
| 20:08:14.780 | e83b95ca-0ef8-45e2-8947-842c382e9560 | **4,097ms** | 1ms | 6ms | 3ms | **4,036ms** | 51ms |
| 20:10:37.600 | 711d37f9-8a99-4bb8-a7f0-85c11d6b0b86 | **4,080ms** | 5ms | 6ms | 3ms | **4,018ms** | 48ms |
**平均值**:
- 总耗时: **4,021ms**
- ensureEngine: **3,975ms**
- chat: **37ms**
---
## 二、性能对比分析
### 2.1 优化前后对比
```
优化前 (cdb41fd):
┌─────────────────────────────────────────────────────────────┐
│ 总耗时: 9,017ms │
│ ├── parseBody: 1ms (<1%) │
│ ├── validate: 6ms (<1%) │
│ ├── ensureWorkspace: 3ms (<1%) │
│ ├── ensureEngine: 8,948ms (99%) ← 瓶颈 │
│ │ ├── parseCCtxServers: 1,732ms │
│ │ ├── syncMcp: 5,204ms (58%) ← 最大瓶颈 │
│ │ └── getOrCreate: 2,006ms │
│ └── chat: 59ms (<1%) │
└─────────────────────────────────────────────────────────────┘
优化后 (12a102b):
┌─────────────────────────────────────────────────────────────┐
│ 总耗时: 4,021ms (↓ 55%) │
│ ├── parseBody: 2ms (<1%) │
│ ├── validate: 4ms (<1%) │
│ ├── ensureWorkspace: 3ms (<1%) │
│ ├── ensureEngine: 3,975ms (99%) ← 仍占大头但显著降低 │
│ │ └── 并行执行 syncMcp + ensureMemoryReady │
│ └── chat: 37ms (<1%) │
└─────────────────────────────────────────────────────────────┘
```
### 2.2 关键优化点
根据 [FIRST-MESSAGE-PERF-OPT-2026-03-23.md](./FIRST-MESSAGE-PERF-OPT-2026-03-23.md) 的实现:
1. **MCP Proxy Bridge 预热** (warmupMcpBridge)
- 应用启动时异步预热
- 减少首次 syncMcp 时间
2. **并行执行** (Promise.all)
- `syncMcpConfigToProxyAndReload()``ensureMemoryReady()` 并行
- 节省串行等待时间
3. **引擎预热策略**
- 提前初始化引擎环境
- 减少首次请求时的冷启动时间
---
## 三、与 macOS 平台对比
### 3.1 历史数据回顾 (macOS)
来自 [DEEP-RESEARCH-PERF-REPORT-2026-03-20.md](../DEEP-RESEARCH-PERF-REPORT-2026-03-20.md):
| 会话 | 时间 | 总耗时 | ensureEngine | 状态 |
|-----|------|--------|--------------|------|
| #4 | 10:15:24.141 | 7,492ms | 7,409ms | 冷启动 |
| #5 | 11:09:48.812 | 7,822ms | 7,734ms | 复用 |
| #6 | 12:19:52.196 | 7,800ms | 7,708ms | 复用 |
| #7 | 12:24:12.225 | 8,005ms | 7,919ms | 复用 |
| #8 | 12:25:32.661 | 7,908ms | 7,815ms | 复用 |
**macOS 平均**: 总耗时 ~7,800msensureEngine ~7,700ms
### 3.2 跨平台对比
```
性能对比 (首消息总耗时):
macOS (历史基准)
├── 冷启动: ~7,500ms
└── 复用: ~7,900ms
Windows 优化前
└── 冷启动: ~9,000ms (慢 15-20%)
Windows 优化后 ✅
└── 冷启动: ~4,000ms (快 48%)
```
### 3.3 平台差异分析
| 因素 | macOS | Windows | 影响 |
|-----|-------|---------|------|
| 文件系统 | APFS | NTFS | Windows 略慢 |
| 进程启动 | 快 | 较慢 | Windows 进程启动开销大 |
| UV/Node 缓存 | 高效 | 略慢 | Windows 路径处理较慢 |
| 优化效果 | - | 显著 | Windows 优化后反超 |
---
## 四、结论与建议
### 4.1 结论
1. **优化效果显著**: Windows 平台首消息耗时从 ~9s 降至 ~4s**提升 55%**
2. **超越 macOS**: 优化后 Windows (~4s) 比历史 macOS (~7.8s) **快 48%**
3. **引擎启动仍是瓶颈**: ensureEngine 仍占总耗时 99%,但已从 8.9s 降至 4s
### 4.2 建议
1. **保持当前优化**: 并行执行策略效果显著,建议保留
2. **进一步预热**: 考虑在应用启动时预热引擎本身(不仅是 MCP Bridge
3. **监控生产环境**: 持续收集 Windows 用户性能数据
4. **macOS 同步优化**: 将 Windows 的优化策略同步到 macOS 版本
---
## 附录:原始日志摘录
### 优化前 (cdb41fd)
```
[2026-03-23 20:17:22.258] [debug] ⏱️ [getOrCreateEngine][PERF] 总耗时: 1001ms (mmemory=263ms, evict=1ms, init=737ms)
[2026-03-23 20:17:22.260] [debug] ⏱️ [ensureEngine][PERF] 总耗时: 8945ms (parseCCtxServers=1732ms, syncMcp=5204ms, extractReal=1ms, ensureBridge=2ms, getOrCreate=2006ms)
[2026-03-23 20:17:22.321] [info] ⏱️ [HTTP][PERF] /computer/chat 总耗时: 9017ms (parseBody=1ms, validate=6ms, ensureWorkspace=3ms, ensureEngine=8948ms, chat=59ms)
```
### 优化后 (12a102b)
```
[2026-03-23 20:10:37.600] [info] ⏱️ [HTTP][PERF] /computer/chat 总耗时: 4080ms (parseBody=5ms, validate=6ms, ensureWorkspace=3ms, ensureEngine=4018ms, chat=48ms)
```
---
*报告生成时间: 2026-03-23*
*对比基准: macOS DEEP-RESEARCH-PERF-REPORT-2026-03-20.md*