1260 lines
84 KiB
Markdown
1260 lines
84 KiB
Markdown
# GoodBuddy 产品、性能与体验综合改进计划
|
||
|
||
## 文档信息
|
||
|
||
| 项目 | 内容 |
|
||
| --- | --- |
|
||
| 文档类型 | 跨功能实施路线图 |
|
||
| 状态 | 规划中 |
|
||
| 版本 | 1.0 |
|
||
| 日期 | 2026-08-31 |
|
||
| 适用范围 | GoodBuddy Desktop、GoodBuddy Agent、发布与质量流程 |
|
||
| 评审基线 | Desktop `0.11.13`、Agent lock `0.11.14` |
|
||
|
||
## 1. 目的
|
||
|
||
本文把 2026-08-31 对当前仓库进行的功能、性能、体验和交付评审结果整理为一份可分批实施、
|
||
持续更新的权威清单。后续每一批工作都从本文选择明确范围,完成后更新状态、验证结果和相关
|
||
产品文档,不再维护平行的临时待办。
|
||
|
||
本计划优先完成已经存在但尚未形成真实用户闭环的能力,其次处理经过测量确认的性能问题,
|
||
再安排依赖外部平台或产品选择的增强能力。实施继续遵循以下原则:
|
||
|
||
1. 先保证完整、可用的生产路径,再扩展能力范围。
|
||
2. 性能优化必须有基线、目标和优化后的对比数据。
|
||
3. 复用现有合同、IPC、Worker、通知、错误边界和界面组件,不引入不必要的框架。
|
||
4. Ask 保持只读;Execute 继续代表当前本机或 SSH 账号的完整授权。
|
||
5. 每批只处理列明的范围,不借机进行无关重构。
|
||
|
||
## 2. 明确排除的工作
|
||
|
||
本计划**不包含初始 Renderer Bundle 优化**,包括但不限于:
|
||
|
||
- 不为了缩小入口包继续拆分轻量同级页面。
|
||
- 不单独延迟 KaTeX、全局样式或其他入口依赖。
|
||
- 不调整现有 Bundle 预算来制造通过或失败。
|
||
- 不以 Bundle 大小为理由改变当前页面交互语义。
|
||
|
||
现有构建预算和按路由拆包继续作为回归保护保留。只有后续获得首屏解析或执行耗时证据,并由
|
||
用户单独确认范围后,才重新讨论该项。
|
||
|
||
## 3. 状态与优先级
|
||
|
||
### 3.1 状态
|
||
|
||
| 状态 | 含义 |
|
||
| --- | --- |
|
||
| 待开始 | 范围已确认,尚未实施 |
|
||
| 进行中 | 已进入当前实施批次 |
|
||
| 阻塞 | 存在明确外部依赖,已记录解除条件 |
|
||
| 已完成 | 代码、真实路径、测试和文档均完成 |
|
||
| 不做 | 经重新评审后明确移出范围,并记录原因 |
|
||
|
||
### 3.2 优先级
|
||
|
||
| 优先级 | 含义 |
|
||
| --- | --- |
|
||
| P0 | 影响正式交付、安装或已声明生产路径,发布前处理 |
|
||
| P1 | 阻断核心工作流或造成明显错误、卡顿、误导 |
|
||
| P2 | 重要增强或规模化问题,可在核心闭环后实施 |
|
||
|
||
## 4. 分批实施总览
|
||
|
||
批次表示建议顺序,不表示必须一次完成整批。每次可以从当前批次选择一组边界清晰、能够独立
|
||
验收的事项。
|
||
|
||
| 批次 | 主题 | 事项 | 开始条件 | 完成门槛 |
|
||
| --- | --- | --- | --- | --- |
|
||
| 0 | 正式交付闭环 | `REL-01` 至 `REL-03`、`QA-03` | 发布资源和目标平台明确 | 正式产物可安装、可启动,版本与验证声明准确 |
|
||
| 1 | 核心工作流与恢复体验 | `FUN-01`、`FUN-04`、`UX-01` 至 `UX-12`、`OPS-01` 至 `OPS-03` | 无 | 用户可理解状态、从常见失败中直接恢复,并能导出有界诊断 |
|
||
| 2 | 通道、更新和真实 Runtime | `FUN-02`、`FUN-03`、`FUN-05`、`QA-01`、`QA-02` | 对应平台测试配置可用 | 文件办公闭环、更新下载闭环和真实 Runtime E2E 均通过 |
|
||
| 3A | 性能基线 | `PERF-01`、`PERF-03`、`PERF-05`、`PERF-07`、`PERF-09` | 固定测试数据和设备 | 形成可重复基线、目标阈值和原始结果 |
|
||
| 3B | 已测量性能优化 | `PERF-02`、`PERF-04`、`PERF-06`、`PERF-08`、`PERF-10` | 对应 3A 指标确认存在瓶颈 | 指标改善且功能、取消、恢复语义不回退 |
|
||
| 3C | 架构级界面性能改造 | `PERF-11` 至 `PERF-17` | `PERF-11` 完整 App 基线已产生 | 第 7.1 节五条架构规则成立,并由基准和 lint 持续守护 |
|
||
| 4 | 增强能力 | `FUN-06` 至 `FUN-09` | 核心闭环稳定;首个电脑控制平台已选定 | 每项形成独立、完整、可真实使用的工作流 |
|
||
|
||
## 5. 发布与交付
|
||
|
||
### REL-01 Desktop 与 Agent 版本闭环
|
||
|
||
- **优先级 / 状态:** P0 / 待开始
|
||
- **目标:** 当前 Desktop 源码依赖的 Agent 能从正式渠道取得并完成真实 Host 验证。
|
||
- **范围:**
|
||
- 发布并验证 Agent `0.11.14`,或在 Desktop 发布前恢复到已发布 Agent 契约。
|
||
- 验证 Linux x64、Linux arm64 正式候选包。
|
||
- 覆盖安装、Attach、Execute、取消和断线恢复。
|
||
- 覆盖 GitHub、镜像和离线 GoodBuddy 传输路径。
|
||
- **验收:**
|
||
- 干净用户目录能获取预期版本。
|
||
- Desktop 到 Agent 的正常路径和恢复路径均使用当前候选产物通过。
|
||
- 发布说明准确标注已验证架构和仍未验证范围。
|
||
- **外部依赖:** Agent 发布审批、双架构正式产物、真实 Linux arm64 环境。
|
||
- **起始证据:** `package.json`、`agent-runtime-lock.json`、
|
||
`docs/features/remote-host/technical-design.md`。
|
||
|
||
### REL-02 正式 Latest 签名门槛
|
||
|
||
- **优先级 / 状态:** P0 / 待开始
|
||
- **目标:** 官网正式推荐下载不指向可能被系统默认安全策略阻止的测试产物。
|
||
- **范围:**
|
||
- macOS 缺少签名或公证凭据时,不更新正式 Latest。
|
||
- Windows 未签名构建不作为默认推荐安装包。
|
||
- 未签名构建继续作为明确标注的测试 artifact。
|
||
- **验收:**
|
||
- 发布工作流在签名条件不满足时不会切换正式 Latest。
|
||
- 正式下载在全新 macOS、Windows 默认安全策略下完成首次启动。
|
||
- **外部依赖:** Apple 和 Windows 代码签名凭据。
|
||
- **起始证据:** `.github/workflows/packages.yml`、`BUILD.md`、`README.md`。
|
||
|
||
### REL-03 发布包启动冒烟
|
||
|
||
- **优先级 / 状态:** P0 / 待开始
|
||
- **目标:** 发布成功代表打包应用能够启动,而不只是文件结构完整。
|
||
- **范围:**
|
||
- 在对应 CI Runner 启动解包应用。
|
||
- 等待 Main、Preload、Renderer ready 健康标识后退出。
|
||
- Windows x64 接入现有 packaged DeepSeek Harness smoke。
|
||
- 为启动设置总超时,并保留无用户内容的失败诊断。
|
||
- **验收:**
|
||
- 缺 DLL、ASAR unpack、Preload 或 Main 启动错误会使发布任务失败。
|
||
- 探针无凭据、无外网也能执行。
|
||
- **起始证据:** `.github/workflows/packages.yml`、`build/build-release.cjs`、
|
||
`build/run-packaged-deepseek-harness-smoke.cjs`。
|
||
|
||
## 6. 核心与增强功能
|
||
|
||
### FUN-01 定时任务编辑
|
||
|
||
- **优先级 / 状态:** P1 / 待开始
|
||
- **目标:** 用户无需删除并重建计划任务即可纠正配置。
|
||
- **范围:**
|
||
- 新增共享 Update schema、Preload 方法和可信 IPC handler。
|
||
- 复用定制任务创建对话框作为编辑模式。
|
||
- 支持修改名称、Prompt、Ask/Execute、Runtime、首次运行时间和周期。
|
||
- 保留原 Task、Conversation、Schedule 和历史运行关联。
|
||
- **验收:**
|
||
- 从任务界面进入编辑,保存后列表和下一次运行时间立即更新。
|
||
- 编辑不产生重复 Task 或 Schedule。
|
||
- 正在运行的实例继续使用启动时快照,后续触发使用新配置。
|
||
- **起始证据:** `CustomTaskDialog.tsx`、`TaskScheduleActions.tsx`、`src/main/ipc.ts`。
|
||
|
||
### FUN-02 企业微信附件收发
|
||
|
||
- **优先级 / 状态:** P1 / 待开始
|
||
- **目标:** 企业微信完成“接收文件或图片、处理、回传成果”的办公闭环。
|
||
- **范围:**
|
||
- 接收官方接口支持的图片和普通文件。
|
||
- 将附件接入现有远程消息上下文。
|
||
- 支持回传当前任务生成的图片或文件。
|
||
- 复用 `ChannelMediaAttachment`、现有大小限制和 outbox 清理。
|
||
- **验收:**
|
||
- 收到支持的附件不会再返回 `attachment_not_supported`。
|
||
- 任务生成的有界成果能发送到原会话。
|
||
- 超限、下载失败和发送失败提供明确错误且临时文件得到清理。
|
||
- **起始证据:** `src/main/channels/wecom-driver.ts`、
|
||
`src/main/channels/wechat-sidecar.ts`。
|
||
|
||
### FUN-03 钉钉附件收发
|
||
|
||
- **优先级 / 状态:** P1 / 待开始
|
||
- **目标:** 钉钉非文本消息不再静默丢失,并完成文件办公闭环。
|
||
- **范围:**
|
||
- 支持官方接口允许的图片和普通文件接收。
|
||
- 支持任务成果附件回传。
|
||
- 对暂不支持的消息类型返回明确说明。
|
||
- **验收:**
|
||
- 支持的非文本消息进入对应远程 Conversation。
|
||
- 不支持的消息有可见反馈,不被静默忽略。
|
||
- 收发限制和清理行为与其他通道一致。
|
||
- **起始证据:** `src/main/channels/dingtalk-driver.ts`、
|
||
`src/main/channels/dingtalk-channel-driver.ts`。
|
||
|
||
### FUN-04 浏览器错误分类
|
||
|
||
- **优先级 / 状态:** P1 / 待开始
|
||
- **目标:** 浏览器任务失败后,用户知道失败阶段和下一步操作。
|
||
- **范围:**
|
||
- 保留代理、DNS、会话创建、CDP、导航和截图等失败阶段。
|
||
- 增加少量稳定错误码及一句修复建议。
|
||
- 在任务记录和界面中呈现,不建立通用错误协议框架。
|
||
- **验收:**
|
||
- 典型失败不再全部显示为“工具执行失败”。
|
||
- 原始敏感内容不进入 Renderer 或诊断日志。
|
||
- 取消继续被识别为取消,而不是错误。
|
||
- **起始证据:** `src/main/agent/model-runtime.ts`、
|
||
`docs/status/computer-control-implementation-status.md`。
|
||
|
||
### FUN-05 更新下载闭环
|
||
|
||
- **优先级 / 状态:** P1 / 待开始
|
||
- **目标:** 用户能在应用中取得当前平台安装文件并确认文件完整。
|
||
- **范围:**
|
||
- 从当前更新源读取当前平台产物。
|
||
- 下载到用户选择目录。
|
||
- 使用现有发布清单校验大小和 SHA-256。
|
||
- 校验成功后允许打开文件或所在目录。
|
||
- 安装继续由用户手动触发。
|
||
- **验收:**
|
||
- 下载源、版本、架构和当前系统匹配。
|
||
- 损坏或哈希不匹配的文件不会被呈现为可安装。
|
||
- 失败保留目标和错误上下文,可重新下载。
|
||
- **起始证据:** `UpdateSettingsSection.tsx`、`src/main/version-checker.ts`、
|
||
`src/main/ipc.ts`。
|
||
|
||
### FUN-06 定时任务常用周期
|
||
|
||
- **优先级 / 状态:** P2 / 待开始
|
||
- **目标:** 覆盖常见办公日历,而不要求用户编写 Cron。
|
||
- **范围:** 增加工作日和每月周期,并持续显示下一次实际执行时间。
|
||
- **验收:** 创建、编辑、重启恢复和到期触发均按本地日历执行且不重复。
|
||
- **依赖:** `FUN-01`、`FUN-07`。
|
||
- **起始证据:** `src/shared/assistant-contracts.ts`、
|
||
`docs/features/task-and-job/scheduled-task-prd.md`。
|
||
|
||
### FUN-07 时区和错过执行策略
|
||
|
||
- **优先级 / 状态:** P2 / 待开始
|
||
- **目标:** 用户明确控制任务在哪个时区运行,以及错过后是否补跑。
|
||
- **范围:**
|
||
- 默认本机 IANA 时区。
|
||
- 支持 `skip` 和 `run_once`。
|
||
- 显示 DST 影响后的下一次实际运行时间。
|
||
- **验收:**
|
||
- 重启或系统恢复后不会形成补跑风暴。
|
||
- 时区和策略可创建、编辑、持久化并在界面复现。
|
||
- **起始证据:** `src/main/assistant/assistant-database.ts`、
|
||
`docs/features/task-and-job/scheduled-task-prd.md`。
|
||
|
||
### FUN-08 旧版 Office 文件
|
||
|
||
- **优先级 / 状态:** P2 / 待开始
|
||
- **目标:** DOC、XLS、PPT 能进入聊天附件和知识库解析路径。
|
||
- **范围:**
|
||
- 检测本机 LibreOffice。
|
||
- 使用 headless 转换到 OOXML 或 PDF 后复用现有解析器。
|
||
- 未安装时提供明确安装或手工转换建议。
|
||
- **验收:**
|
||
- 三种旧格式各用真实文件完成聊天解析和知识库导入。
|
||
- 转换取消、超时和失败后清理 GoodBuddy 临时目录。
|
||
- 不内置第二套文档解析引擎。
|
||
- **起始证据:** `src/main/knowledge/document-parser.ts`、
|
||
`DocumentParsingSettingsSection.tsx`。
|
||
|
||
### FUN-09 首个原生电脑控制适配器
|
||
|
||
- **优先级 / 状态:** P2 / 待开始
|
||
- **目标:** 在一个明确的主要部署平台上真实控制桌面应用。
|
||
- **范围:**
|
||
- 首先从 Windows UIA、macOS AX 或 Linux 原生方案中选择一个平台。
|
||
- 完成窗口快照、点击、输入、取消和退出清理。
|
||
- 接入现有 computer-control broker。
|
||
- **验收:**
|
||
- 在真实桌面应用完成一次可重复的查看、点击和输入任务。
|
||
- Ask 仍不可执行桌面变更,Execute 路径记录活动。
|
||
- 取消和关闭后不遗留 GoodBuddy 控制会话。
|
||
- **外部依赖:** 首个平台产品决策及对应真实设备。
|
||
- **起始证据:** `src/main/capabilities/capability-service.ts`、
|
||
`docs/status/computer-control-implementation-status.md`。
|
||
|
||
## 7. 性能
|
||
|
||
### PERF-01 启动性能测量
|
||
|
||
- **优先级 / 状态:** P1 / 待开始
|
||
- **目标:** 得到冷启动各阶段的可重复耗时,而不是依据代码顺序猜测。
|
||
- **指标:** `app.whenReady`、窗口创建、数据库、知识库、Provider/Gateway、Runtime hydration、
|
||
IPC 注册、`loadMainWindow`、`ready-to-show` 和 Renderer 首帧。
|
||
- **验收:** 固定设备至少运行五次冷启动,报告中位数和 p95;诊断不包含用户内容。
|
||
|
||
### PERF-02 启动路径优化
|
||
|
||
- **优先级 / 状态:** P1 / 待开始
|
||
- **目标:** 尽早显示可理解的应用壳,并延后经测量确认的非首屏工作。
|
||
- **实施条件:** `PERF-01` 证明具体阶段构成主要延迟。
|
||
- **验收:** 首帧和可交互时间达到批次开始时确定的目标,初始化失败仍有明确状态和恢复入口。
|
||
|
||
### PERF-03 长流式回答基准
|
||
|
||
- **优先级 / 状态:** P1 / 待开始
|
||
- **场景:** 100KB、500KB、1MB Markdown 回答,包含推理、代码块和常见富文本。
|
||
- **指标:** React commit 数和 p95、Long Tasks、Markdown 渲染时间、输入延迟、滚动帧率、
|
||
Renderer 堆内存。
|
||
- **验收:** 测试数据、运行步骤和原始结果可重复执行。
|
||
|
||
#### 2026-09-28 Measurement Evidence (Partial)
|
||
|
||
`npx vitest run tests/streaming-markdown.electron.test.ts` runs the real
|
||
MarkdownRenderer in Electron 43.2.0 / Chromium 150.0.7871.129. It compares the
|
||
original normalizer with fast paths that skip unchanged text, retaining live
|
||
Markdown, math, code and HTML behavior. It also measures a 102,400-byte answer
|
||
at six growing prefixes, alternating original/current execution order in the
|
||
same page, with one warm-up sweep and three measured sweeps (18 samples each).
|
||
|
||
| Measurement | Original | Current |
|
||
| --- | --- | --- |
|
||
| 100KiB growing-prefix render median, isolated run | 54.80 ms | 54.65 ms |
|
||
| Same run p95 | 95.50 ms | 106.10 ms |
|
||
| Growing-prefix render median, full-suite run | 53.50 ms | 53.10 ms |
|
||
| Same run p95 | 93.70 ms | 83.40 ms |
|
||
|
||
These development-mode measurements include synchronous React rendering, DOM
|
||
commit and style/layout, but exclude frame waiting and paint. With 18 samples,
|
||
nearest-rank p95 is the maximum sample. The results do not establish an overall
|
||
rendering improvement. Smaller 6.4-7.2K-character corpora do show lower normalizer
|
||
cost: prose median 33-34 microseconds to below 1 microsecond, and mixed math/code
|
||
median 32-35 to 16-20 microseconds. The production changes remain limited to this
|
||
preprocessing and memoizing unchanged subagent-derived collections. In the
|
||
four-subagent regression, initial render plus six updates builds the lookup map
|
||
once instead of seven times; block-dependent scans still run when blocks change.
|
||
|
||
Validation: 550 prefix normalization comparisons, 1,100 short-prefix DOM
|
||
comparisons and 24 long-prefix DOM comparisons passed. The real Electron
|
||
terminal-status probe covers cancellation, failure, retry and persistence via
|
||
three loopback model-stub requests; the Mermaid viewer covers nine scenarios
|
||
and ten PNG downloads. No external model calls were used for these probes.
|
||
Full `npm test` passed 459 files / 5,397 tests, with 11 files / 84 tests skipped.
|
||
Type checking and scoped lint passed. Full lint remains blocked by the existing
|
||
untracked sidebar-final probe files.
|
||
|
||
This is partial evidence for PERF-03, not completion: full-App input latency,
|
||
frame rate, heap measurements and 500KB/1MB scenarios remain unmeasured.
|
||
|
||
### PERF-04 流式更新合并
|
||
|
||
- **优先级 / 状态:** P1 / 待开始
|
||
- **目标:** 降低长回答期间的状态复制和完整 Markdown 重解析。
|
||
- **实施条件:** `PERF-03` 确认 commit 或 Markdown 渲染是瓶颈。
|
||
- **范围:**
|
||
- 同一请求的 text/reasoning delta 按 `requestAnimationFrame` 合并。
|
||
- 每帧最多提交一次相关 React 状态。
|
||
- 流结束后再执行完整富文本渲染。
|
||
- **验收:** 目标指标改善;事件顺序、停止、重试、引用、工具和 Subagent 块不回退。
|
||
|
||
### PERF-05 向量检索规模基准
|
||
|
||
- **优先级 / 状态:** P1 / 耗时基线已完成,取消响应与峰值内存待测
|
||
- **场景:** 1k、10k、50k chunks 的全文、向量和混合检索。
|
||
- **指标:** 总耗时、`vectorScannedCount`、Main event-loop lag、取消响应和峰值内存。
|
||
- **验收:** 固定语料和查询集产生可比较结果,并保留检索质量指标。
|
||
|
||
#### 2026-10-02 耗时基线
|
||
|
||
`npm run perf:knowledge`(`build/run-knowledge-perf.cjs`)用 esbuild 打包生产
|
||
`KnowledgeDatabase`,在临时 SQLite 中写入确定性随机语料(每文档 100 chunks,每 chunk 80 词,
|
||
384 维向量,与内置嵌入模型一致),重开连接后对每个规模各测 15 次 `search`、`vectorSearch`
|
||
和 `hybridSearchWithDiagnostics`(图谱关闭)。这些调用是同步的,在 App 中由 Main 线程直接
|
||
执行,所以单次耗时就是 Main 被阻塞的时长。同一台设备(Node 24.18),p95:
|
||
|
||
| chunks | 全文 | 向量 | 混合 |
|
||
| --- | --- | --- | --- |
|
||
| 5,000 | 14 ms | 54 ms | 93 ms |
|
||
| 20,000 | 59 ms | 173 ms | 219 ms |
|
||
| 50,000 | 130 ms | 396 ms | 520 ms |
|
||
|
||
向量扫描与 chunk 数线性相关,FTS 也随规模增长。按 50 ms 预算,约 5k chunks 起混合检索就会
|
||
使 Main 可感知地卡顿;5 万 chunks 时每次检索约冻结 Main 0.5 s,期间全部 IPC(包括流式输出、
|
||
终端和界面操作)排队。结论:`PERF-06` 的实施条件已满足;检索应先移出 Main(`PERF-06`,
|
||
随后并入 `PERF-16`)。取消响应和峰值内存尚未测量。
|
||
|
||
### PERF-06 向量计算移出 Main
|
||
|
||
- **优先级 / 状态:** P1 / 待开始
|
||
- **目标:** 大知识库向量扫描不阻塞 Electron Main。
|
||
- **实施条件:** `PERF-05` 确认 Main event-loop lag 或取消响应不达标。
|
||
- **范围:** 优先复用已有 Worker,减少重复扫描和复制;本项不自动引入 ANN。
|
||
- **验收:** 检索排序和质量不回退,Main 延迟与取消指标达到目标。
|
||
|
||
### PERF-07 知识库 Snapshot 测量
|
||
|
||
- **优先级 / 状态:** P2 / 待开始
|
||
- **指标:** SQL 时间、对象数量、structured-clone 估算字节、IPC 往返、Renderer commit 和
|
||
峰值堆内存。
|
||
- **场景:** 小型、典型和大型文档与图谱数据集。
|
||
- **验收:** 明确首屏实际需要的字段和超过目标阈值的数据规模。
|
||
|
||
### PERF-08 知识库按需加载
|
||
|
||
- **优先级 / 状态:** P2 / 待开始
|
||
- **实施条件:** `PERF-07` 证明完整 Snapshot 是可见瓶颈。
|
||
- **范围:**
|
||
- 首屏只返回列表字段、状态和计数。
|
||
- 文档详情按选择加载。
|
||
- 图实体、关系和证据仅在图谱视图加载。
|
||
- 复用已有列表和 chunk 接口。
|
||
- **验收:** 首开与切换指标改善,选择、刷新、取消和局部错误状态不回退。
|
||
|
||
### PERF-09 Keep-alive 压力测试
|
||
|
||
- **优先级 / 状态:** P2 / 文本会话部分已完成,图片与工具记录待补
|
||
- **场景:** 连续打开 12 个会话,每个会话包含 80 条富 Markdown、图片和工具记录。
|
||
- **指标:** DOM 节点、Renderer heap、GC pause 和流式 commit。
|
||
- **验收:** 明确隐藏会话缓存是否构成实际内存或渲染问题。
|
||
|
||
#### 2026-10-02 文本会话测量
|
||
|
||
`npm run perf:app` 在流式场景之后连续打开 24 个未访问过的种子会话(每个 20 条富 Markdown),
|
||
每 4 次记录挂载的面板数、DOM 节点数和强制 GC 后的 JS heap(CDP
|
||
`HeapProfiler.collectGarbage` + `Runtime.getHeapUsage`)。
|
||
|
||
| 已访问 | 挂载面板 | DOM 节点 | GC 后 heap |
|
||
| --- | --- | --- | --- |
|
||
| 0(两次流式之后) | 12 | 39,657 | 54.6 MB |
|
||
| 4 / 8 | 12 | 39,656 | 54.6–55.1 MB |
|
||
| 12 | 12 | 15,884 | 27.4 MB |
|
||
| 16 / 20 / 24 | 12 | 15,884 | 27.3–27.9 MB |
|
||
|
||
缓存上限 12 个面板有效:两条长流式回答被挤出缓存后,DOM 和 heap 回落并保持不变,后半段
|
||
增长为 0。此前观察到的“4 万节点、210 MB”是两条 40 KB 流式回答仍在缓存中时的瞬时值
|
||
(未 GC),不是持续泄漏。结论:文本会话场景下 keep-alive 不构成内存问题,`PERF-10` 不需要
|
||
调整缓存数量或有效期;含图片和工具记录的会话尚未测。
|
||
|
||
### PERF-10 Keep-alive 轻量优化
|
||
|
||
- **优先级 / 状态:** P2 / 已完成输入与热切换渲染隔离,压力测试待完成
|
||
- **实施依据:** 2026-09-30 的真实 App 组件回归确认输入和热切换会重复渲染缓存内容;
|
||
`PERF-09` 的 Electron 内存与 GC 压力测试仍待执行。
|
||
- **范围:**
|
||
- `ChatHistoryPane`、任务条和 Runtime 清单使用 memo,稳定快捷输入、重试、图片恢复、
|
||
定时任务回调及任务条 JSX。异步草稿操作仍绑定发起时的会话。
|
||
- 仍不达标时才调整缓存数量或一小时有效期。
|
||
- 暂不引入完整虚拟化框架。
|
||
- **验收:** 内存和 commit 指标改善,同时保留会话切换位置和快速恢复体验。
|
||
|
||
`App.test.tsx` 修复前回归记录:运行请求期间连续输入三次,历史面板、任务条、清单各
|
||
渲染三次,会话迁移存储读取三次;两个已缓存会话往返一次,Markdown 渲染 324 次。
|
||
长会话已展开 160 条消息,另一会话含一条消息,计数包含展开的推理正文。修复后上述
|
||
计数均为零。最终用例还覆盖可见且展开的运行中任务条、实时文本与清单更新、停止、
|
||
会话草稿、图片素材选择期间切换会话,以及滚动位置和推理展开状态保留。
|
||
这些是 jsdom 中真实 App 的渲染次数,使用 mock preload;尚未测量 Electron 输入延迟、
|
||
绘制耗时或内存。流式事件未延后,缓存容量和有效期未改变。
|
||
|
||
### 7.1 架构级界面性能改造(批次 3C)
|
||
|
||
2026-10-02 的架构评审确认,界面卡顿的主要来源是架构组织方式,而不是 Electron 或 GPU:
|
||
|
||
- **Renderer 单组件状态:** `App.tsx` 约 11k 行,`App()` 内有 123 个 `useState`、69 个
|
||
`useEffect`。会话、消息、草稿、任务和设置都在其中,输入和每次流式 flush 都会重新执行
|
||
整个 App 函数体;多个大面板未 memo,隐藏的 keep-alive 视图也会参与渲染。
|
||
- **Main 同步存储:** 全部 SQLite 访问使用 Main 线程的 `DatabaseSync`,包括全表向量扫描、
|
||
会话列表与搜索、Story Graph 读取;PDF/Office 解析和附件同步文件 IO 也在 Main。
|
||
- **全量回写:** 活动记录在任何变化后 250 ms 全量 `replace`;会话每 500 ms 定时保存;
|
||
`*Changed` 通知不带内容,Renderer 再重新查询完整列表。
|
||
|
||
改造完成后须同时满足以下规则,并由工具守护,避免回退:
|
||
|
||
1. Main 只负责窗口、生命周期、权限和消息路由,不执行数据库查询、文档解析或批量计算。
|
||
2. Renderer 状态按领域拆成外部 store,组件通过 selector 订阅;`App.tsx` 只保留布局壳。
|
||
3. 流式文本和终端输出走专用通道,只更新正在显示的行或面板,按帧合并提交。
|
||
4. 数据只有一个权威来源,跨进程只传增量;不再全量回写,也不再“通知后重新查全量”。
|
||
5. 消息、会话、笔记、文件树等长列表虚拟化,DOM 数量不随数据量增长。
|
||
|
||
实施方式:先建立 `PERF-11` 基线,再沿 Renderer(`PERF-12`→`PERF-13`→`PERF-14`)和
|
||
数据进程(`PERF-15`→`PERF-16`)两条线并行推进,最后由 `PERF-17` 固化守护。每一步完成后
|
||
重新运行 `PERF-11` 场景并记录对比;不换技术栈,不一次性重写 `App.tsx`。
|
||
|
||
### PERF-11 完整 App 交互基准
|
||
|
||
- **优先级 / 状态:** P1 / 进行中
|
||
- **目标:** 在当前生产构建、隔离 profile 和本地假模型下,可重复测量用户能感知的卡顿。
|
||
- **运行:** `npm run build:bundle` 后执行 `npm run perf:app`;结果写入
|
||
`GB_PERF_OUTPUT` 或系统临时目录,不含用户内容,不发出外部模型请求。
|
||
- **场景:** 空闲基线;Composer 连续输入;切换会话;打开长会话;大量会话列表下的流式
|
||
回答;流式回答期间同时输入。
|
||
- **指标:** Renderer Long Tasks(>50 ms)数量与总时长、按键到下一帧延迟、帧间隔 p95 与
|
||
掉帧比例、React commit 无法直接取得时以 DOM mutation 批次近似、Main event-loop 延迟
|
||
(`monitorEventLoopDelay`)、Main 与 Renderer 进程 CPU 和内存、Renderer JS heap。
|
||
- **验收:** 同一设备多次运行结果可比较;报告记录源码版本、环境、数据规模和原始样本摘要。
|
||
|
||
#### 实现
|
||
|
||
- `build/run-app-perf.cjs`:独立 supervisor。在 `127.0.0.1:11434`(新 profile 默认模型地址)
|
||
启动 OpenAI 兼容假模型,清除 `ELECTRON_RUN_AS_NODE` 和模型环境变量,设置整轮期限(默认
|
||
600 s),汇总报告并删除隔离 profile。端口被占用时直接失败。
|
||
- `tests/support/app-perf-driver.mjs`:在导入 `out/main/index.js` 前设置 `appPath`、
|
||
`userData`、`sessionData` 和日志目录,然后由生产 Main 创建窗口、注册 IPC 并加载真实
|
||
Preload/Renderer。全部交互使用 `sendInputEvent` 原生输入;数据通过生产
|
||
`conversations.saveLocal` IPC 写入,再 reload 走正常启动加载。
|
||
- Renderer 探针只观察:`longtask`、`long-animation-frame`、Event Timing、rAF 帧间隔、
|
||
keydown 到下一帧、MutationObserver 计数、DOM 节点数和精确 JS heap。Main 用
|
||
`monitorEventLoopDelay`,进程用 `app.getAppMetrics()`。
|
||
- 默认规模:300 个会话 × 20 条消息,另加一个 2,000 条消息的长会话;输入 120 个字符,
|
||
按键间隔 60 ms;流式回答约 40 KB 混合 Markdown(标题、列表、代码、表格、行内/块级公式、
|
||
引用),每个 SSE 事件 12 个字符,2,000 字符/秒。可用 `GB_PERF_*` 环境变量调整,
|
||
见脚本开头。
|
||
- `GB_PERF_PROFILE=1` 为有历史输入和流式场景采集 Renderer CPU profile 并汇总自身耗时
|
||
热点;剖析有开销,只用于归因,不作为基线数据。
|
||
- `GB_PERF_CPU_THROTTLE=<倍数>` 用 CDP 降低 Renderer CPU 速度模拟低端设备;
|
||
`GB_PERF_MEMORY_VISITS`(默认 24,0 跳过)控制 `PERF-09` 内存场景。
|
||
|
||
#### 2026-10-02 基线
|
||
|
||
源码 `8280158`(工作区含其他未提交改动),Windows x64,Intel Core Ultra X7 358H ×16,
|
||
32 GB,Electron 43.2.0 / Chromium 150,DPR 1,内容区 1280×800,GPU 合成与光栅化均启用。
|
||
连续运行 3 次,假模型请求每次 2 次,外部模型请求 0 次。帧间隔基线为 8.3 ms(120 Hz)。
|
||
|
||
| 场景 | Long Tasks 次数 / 总时长 | 帧间隔 p95 | 掉帧比例 | 交互延迟 p50 / p95 | Main lag 最大 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| 空闲(新 profile / 有历史) | 0 / 0 | 8.5 ms | 0% | — | 17–21 ms |
|
||
| 输入 120 字(新 profile) | 0 / 0 | 8.5 ms | ≤0.2% | 按键→帧 4.8–5.1 / 8.3–8.5 ms | ≤17 ms |
|
||
| 输入 120 字(有历史) | 0 / 0 | 8.5 ms | 0% | 按键→帧 6.5–6.7 / 8.4–8.7 ms | ≤18 ms |
|
||
| 首次切换会话 ×15 | 1 / 77–81 ms | 8.6 ms | 3.7–4.5% | 点击→显示 49–50 / 102–105 ms | 29–32 ms |
|
||
| 再次切换(缓存命中)×10 | 0 / 0 | 8.6 ms | 3.3–4.1% | 点击→显示 52–53 / 56–59 ms | 19–22 ms |
|
||
| 打开 2,000 条消息会话 | 1 / 94–102 ms | 8.5 ms | 1.6% | 点击→显示 134–144 ms | ≤21 ms |
|
||
| 滚动长会话 3 s | 0 / 0 | 8.5 ms | 0% | — | ≤17 ms |
|
||
| **流式 40 KB 回答** | **126–137 / 8.6–9.3 s** | **83 ms** | **45–48%** | — | 30–36 ms |
|
||
| **流式期间输入 100 字** | **128–182 / 8.8–11.9 s** | **83 ms** | **50–52%** | 按键→帧 12.5–13.5 / 35–36 ms | 29–144 ms |
|
||
|
||
其他观察:
|
||
|
||
- 启动:窗口创建约 0.58 s,`ready-to-show` 约 1.29 s,Composer 可用约 1.42 s;带 300 个
|
||
会话 reload 到侧栏出现约 0.19 s。
|
||
- 流式回答约 20 s,Long Tasks 占记录时间约 40%;按四等分统计为 `0 / 2.5 / 4.7 / 4.6 s`,
|
||
随回答变长而增加,说明每次提交的成本与已生成长度成正比。
|
||
- 有历史时侧栏一次渲染全部 302 行,DOM 约 4,400 节点;访问过若干会话后约 1.6 万;两次
|
||
流式后约 4 万,JS heap 最高约 210 MB(keep-alive 缓存持续增长)。
|
||
- 长会话只渲染最近 80 条,打开耗时主要是一次约 100 ms 的 Long Task。
|
||
- KaTeX 的 `data:` 字体被 CSP 拒绝(缺少 `font-src`),每轮 8 条控制台错误,字体回退
|
||
(已在 `PERF-12` 第一步修复)。
|
||
- `app.getAppMetrics()` 的 CPU 百分比在 Windows 上按全部逻辑核归一,只作相对比较。
|
||
|
||
**归因(剖析运行):** 流式期间 Renderer 自身 CPU 约 74% 在应用 bundle 中,热点是
|
||
`hast-util-from-dom` 的 `parseFragment`/`fromDom`(rehype-katex 把 KaTeX HTML 再解析成
|
||
hast)、micromark 分词、hast→React 转换和 React reconcile。对照实验确认根因:默认语料的
|
||
块级公式写在一行(`$$...$$`),命中 `MarkdownRenderer.tsx` 的 `unsafeSegmentLine`,
|
||
`splitMarkdownSegments` 退回整篇单段,**每次 flush 都重新解析整篇回答并重新渲染全部公式**。
|
||
仅把 `$$` 改成独占一行(`GB_PERF_CORPUS=multiline-math`)后,同一流式场景 Long Tasks
|
||
从约 130 次 / 9 s 降到 1 次 / 0.15 s,帧间隔 p95 从 83 ms 回到 8.5 ms,流式期间按键→帧
|
||
p95 从约 36 ms 降到 15.5 ms。
|
||
|
||
**结论与对后续批次的影响:**
|
||
|
||
1. 当前最严重、用户最易感知的卡顿是**流式长回答**,且由 Markdown 分段失效后的整篇重解析
|
||
主导;输入、切换、滚动和空闲在本机均未出现明显问题。`PERF-12` 应优先处理:让分段在
|
||
单行 `$$`、HTML 行、链接定义等情况下也能安全工作或局部回退,并对已完成段缓存渲染结果。
|
||
2. 假模型 2,000 字符/秒高于常见真实模型输出速度,但成本随长度线性增长,真实速度下回答
|
||
足够长时同样会出现。
|
||
3. 会话首次切换 p95 约 100 ms、打开长会话约 140 ms,可在 `PERF-13`/`PERF-14` 中改善,
|
||
优先级低于第 1 项。
|
||
4. 本机为高性能设备;低端设备上的绝对值会更差,后续应补一台低配 Windows 设备基线。
|
||
|
||
### PERF-12 流式与输入快速止血
|
||
|
||
- **优先级 / 状态:** P1 / 进行中(Markdown 分段已完成)
|
||
- **范围:** 流式 delta 在 Renderer 按 `requestAnimationFrame` 合并后提交,并以 transition
|
||
降低优先级;缓存 Markdown 预处理结果;`RightAssistantSidebar`、`ProjectSwitcher` 和
|
||
keep-alive 路由使用 memo 并稳定回调;滚动跟随只在需要时执行。
|
||
- **验收:** `PERF-11` 流式与输入场景的 Long Tasks 和输入延迟下降;事件顺序、停止、重试、
|
||
工具与 Subagent 块不回退。该项吸收 `PERF-04` 的范围。
|
||
|
||
#### 2026-10-02 第一步:Markdown 分段与 KaTeX 字体
|
||
|
||
- **分段(`MarkdownRenderer.tsx`):** `splitMarkdownSegments` 按 micromark-extension-math
|
||
的实际规则识别块级公式:`$$` 等两个以上 `$` 开头、其后不含 `$` 的行才开启公式块,并需要
|
||
不短于开启长度的 `$` 行关闭;`$$x^2$$` 这类单行公式按普通段落处理,不再使整篇退回单段。
|
||
链接定义和脚注改为逐行检查,位于顶格代码围栏内的行视为字面内容。原始 HTML 行、`\r`、
|
||
缩进围栏内的定义仍整篇退回。
|
||
- **字体(`electron.vite.config.ts`):** Renderer 构建不再把字体内联为 `data:` URL。CSP
|
||
没有 `font-src`,原先 4 个小于 4 KB 的 KaTeX 字体被拒绝并回退;现在 60 个 KaTeX 字体
|
||
全部作为同源文件输出,CSP 不变。
|
||
- **等价性验证:** 现有逐前缀对照语料新增单行公式、长 `$` 围栏、代码围栏内的定义等用例;
|
||
新增 1,200 份确定性随机文档(半数取随机前缀)对照,要求分段渲染与整篇渲染的 DOM 完全
|
||
一致。开发期另跑了约 4.8 万份随机文档与前缀,其中约 6,300 份走分段路径,全部一致。
|
||
- **`npm run perf:app` 对比(同机,各 3 次,`80579ee` + 本改动):**
|
||
|
||
| 场景 | 改动前 | 改动后 |
|
||
| --- | --- | --- |
|
||
| 流式 40 KB:Long Tasks | 126–137 次 / 8.6–9.3 s | 1 次 / 0.16 s |
|
||
| 流式 40 KB:帧间隔 p95 / 掉帧 | 83 ms / 45–48% | 8.6–8.9 ms / 3–5% |
|
||
| 流式期间输入:按键→帧 p50 / p95 | 12.5–13.5 / 35–36 ms | 11.7–11.9 / 19–21 ms |
|
||
| 流式期间输入:掉帧 | 50–52% | 7.7–8.5% |
|
||
| KaTeX 字体 CSP 错误 | 每轮 8 条 | 0 |
|
||
|
||
输入、切换、长会话和滚动场景与基线持平。流式期间输入仍有约 8% 掉帧、按键 p95 约 20 ms,
|
||
剩余开销属于 App 级重渲染,留给 `PERF-12` 后续项和 `PERF-13`。
|
||
- **剩余:** 流式 delta 按帧合并、大面板 memo、滚动跟随优化仍待实施。
|
||
|
||
#### 2026-10-02 低端设备模拟(Renderer CPU 4× 降速)
|
||
|
||
`GB_PERF_CPU_THROTTLE=4 npm run perf:app` 通过 CDP `Emulation.setCPUThrottlingRate` 把
|
||
Renderer 主线程降速 4 倍(Main 不降速,每次 reload 后重新应用),源码 `5dc6545`
|
||
(含 `PERF-12` 第一步),同一设备。不降速一栏为同一轮构建的对照。
|
||
|
||
| 场景 | 不降速 | 4× 降速 |
|
||
| --- | --- | --- |
|
||
| 输入 120 字(新 profile)按键→帧 p95 | 8.7 ms | 42 ms,掉帧 21% |
|
||
| **输入 120 字(有 300 个会话)** | 0 Long Task,p95 9 ms | **37 次 / 3.2 s,p95 355 ms,最大 483 ms** |
|
||
| **首次切换会话 ×15** 点击→显示 p50 / p95 | 52 / 117 ms | **434 / 922 ms** |
|
||
| 再次切换(缓存命中)p50 / p95 | 55 / 66 ms | 482 / 534 ms |
|
||
| 打开 2,000 条消息会话 | 140 ms | 1.2 s |
|
||
| 滚动长会话 | 帧 p95 8.5 ms | 帧 p95 17 ms,正常 |
|
||
| **流式 40 KB** | 1 次 / 0.17 s,帧 p95 17 ms | **156 次 / 16.7 s,帧 p95 183 ms,掉帧 100%** |
|
||
| **流式期间输入** 按键→帧 p50 / p95 | 11.5 / 17 ms | **575 / 1,513 ms** |
|
||
|
||
高性能机上不可见的问题,在降速后全部暴露:
|
||
|
||
- 新 profile 输入只到 42 ms,有 300 个会话后升到 355 ms,差异来自**每次按键重新渲染整个
|
||
App 和完整侧栏**。降速剖析:`resizeComposerTextarea`(每次输入同步读 `scrollHeight` 强制
|
||
布局)约 8%;i18next `formatLanguageCode`/`translate` 约 12%(`Intl.getCanonicalLocales`
|
||
每次调用 `t()` 都执行,侧栏 302 行各自多次调用);其余是 App 与侧栏行的 React 渲染。
|
||
- 流式期间约 18% 是 `ChatHistoryPane` 每次 flush 的滚动跟随 layout effect(读布局后
|
||
`scrollTo`),i18next 约 8%,Markdown 解析与 React reconcile 其余。
|
||
- 缓存命中切换也约 0.5 s,说明慢的不是挂载新面板,而是切换触发的 App 级整体重渲染。
|
||
|
||
**结论:** 低端设备上输入、切换和流式都会明显卡顿,主因是 App 级重渲染和侧栏全量渲染,
|
||
证实 `PERF-13`(领域 store)和 `PERF-14`(侧栏虚拟化)有必要。`PERF-12` 剩余项应先做三处
|
||
低成本热点:Composer 高度改为只在需要时测量、`t()` 结果或 i18next 语言码规范化做缓存、
|
||
滚动跟随按帧合并且只在贴底时执行。
|
||
|
||
#### 2026-10-02 第二步:三处低成本热点
|
||
|
||
- **Composer 高度(`App.tsx` `resizeComposerTextarea`):** 记住上次的值与高度。值未变不测量;
|
||
在原文本末尾追加时不再先设 `height: auto`(只会变高),已到最大高度直接跳过测量;删除、
|
||
替换、清空仍按原方式重置并测量。
|
||
- **i18next 语言码(`i18n/index.ts`):** i18next 25 每次 `t()` 都经
|
||
`toResolveHierarchy`→`formatLanguageCode` 调用 `Intl.getCanonicalLocales`。初始化后对
|
||
`languageUtils.formatLanguageCode` 加按输入缓存(结果只取决于代码与静态初始化选项,上限
|
||
64 项),翻译结果不变。
|
||
- **滚动跟随(`ChatHistoryPane.tsx`):** 激活面板时仍同步滚到底;之后内容更新时改为每帧最多
|
||
一次 `requestAnimationFrame` 中读取 `scrollHeight` 并 `scrollTo`,回调里再次确认仍贴底;
|
||
卸载时取消。读者离开底部时不滚动,恢复阅读位置逻辑不变。
|
||
- **测试:** 新增“流式三次更新只滚动一帧”(旧实现 3 次,失败)、Composer 追加与删除的测量
|
||
次数、`t()` 不再调用 `Intl.getCanonicalLocales`。全量测试 5,621 通过,2 失败与本改动
|
||
无关(真实 OpenCode 集成测试因免费额度返回 403;Supervisor 超时设置用例此前已失败)。
|
||
- **`npm run perf:app` A/B(同机,改动前为临时 worktree 中的 `983cdd4` 构建,交替运行):**
|
||
|
||
| 场景(4× 降速,3 轮) | 改动前 按键/点击 p50 / p95 | 改动后 |
|
||
| --- | --- | --- |
|
||
| 输入 120 字(新 profile) | 28–80 / 33–361 ms | 19–36 / 24–57 ms |
|
||
| 输入 120 字(有 300 个会话) | 265–2,372 / 1,486–4,266 ms | 27–161 / 32–346 ms |
|
||
| 首次切换会话 | 442–761 / 1,063–1,384 ms | 360–398 / 820–1,334 ms |
|
||
| 再次切换(缓存命中) | 472–749 / 501–1,143 ms | 390–420 / 456–951 ms |
|
||
| 流式 40 KB:Long Tasks 总时长 | 15.9–20.6 s | 15.1–17.4 s |
|
||
| 流式期间输入 | 432–6,778 / 1,146–9,634 ms | 96–466 / 297–1,567 ms |
|
||
|
||
不降速时(2 轮)两版各场景持平,输入 p95 均约 9 ms,流式 1 个 Long Task。4× 降速下
|
||
方差很大(同版本轮间可差数倍),应看量级:有历史时输入从秒级回到百毫秒内,切换略有改善,
|
||
流式本身仍是每帧卡顿——剩余成本是 App 级重渲染与 Markdown/React 提交,需 `PERF-13`。
|
||
- **剩余:** 流式 delta 按帧合并后以 transition 提交、大面板 memo 仍待实施;之后进入
|
||
`PERF-13`。
|
||
|
||
### PERF-13 Renderer 领域 Store 迁移
|
||
|
||
- **优先级 / 状态:** P1 / 进行中(流式缓冲、Composer 草稿已迁移)
|
||
- **顺序:** 流式缓冲 → Composer 草稿 → 会话与消息 → 任务与通知 → 设置及其余界面状态。
|
||
- **每个领域的完成步骤:** 建立 store 并把 IPC 订阅移入 store;调用方改为 selector;删除
|
||
App 中对应 state/ref 同步代码;运行测试与 `PERF-11`。
|
||
- **验收:** 输入和流式更新不再重新渲染 App 根组件;`App.tsx` 最终降到布局壳规模。
|
||
|
||
#### 2026-10-02 第一步:流式缓冲与 Composer 草稿
|
||
|
||
- **流式缓冲(`live-message-store.ts`):** `text`/`reasoning` delta 不再逐条 `setConversations`,
|
||
而是写入按消息分组的外部 store;`ChatMessageRow` 用 `useSyncExternalStore` 订阅自己的消息,
|
||
只有这一行重新渲染。App 状态每 250 ms 吸收一次,或在任何其他会话更新时先吸收。
|
||
- 每个 delta 带序号和应用后的正文/思考长度。store 生成的消息对象记录已包含的序号;持久化
|
||
快照等外来��象按长度判断(delta 只追加),因此不会重复或丢失。
|
||
- 一次更新只吸收派发时已到达的 delta,工具、审批等块与文本保持原有顺序。
|
||
- 吸收后裁剪已提交的 delta,行内显示增量折叠,长回答期间缓冲区保持很短。
|
||
- 吸收用普通更新而非 transition:transition 会被行的同步更新反复打断,导致缓冲区一直增长
|
||
(首版实测出现过流式 Long Tasks 翻倍)。
|
||
- **Composer 草稿(`composer-draft-store.ts`):** 按会话的草稿移出 App state;输入框和发送按钮
|
||
分别订阅草稿文本和“是否有内容”,按键不再重渲染 App。高度调整改为草稿提交后的 layout effect。
|
||
- **测试:** store 单测覆盖折叠等价、提交前显示、工具块顺序、派发时快照、替换快照不重复、
|
||
React 重放幂等、无基线不隐藏、长流裁剪;App 测试改为断言流式文本不再渲染历史面板与任务条。
|
||
- **`npm run perf:app` A/B(同机,改动前为临时 worktree 中的 `90ea33e` 构建,交替运行):**
|
||
|
||
| 场景(4× 降速,3 轮) | 改动前 p50 / p95 | 改动后 |
|
||
| --- | --- | --- |
|
||
| 输入 120 字(新 profile) | 18–21 / 24–26 ms,掉帧 7–13% | 9–10 / 11–16 ms,掉帧 0–1% |
|
||
| 输入 120 字(有 300 个会话) | 27–81 / 35–227 ms | 10–19 / 12–36 ms |
|
||
| 流式 40 KB:Long Tasks 总时长 | 6.5–20.8 s | 3.6–19.3 s(3 轮中 2 轮约 4–5 s) |
|
||
| 流式期间输入 | 73–1,808 / 174–3,498 ms | 34–62 / 59–573 ms |
|
||
| 打开 2,000 条消息会话(见下方更正) | 1.07–1.80 s,帧 p95 9–42 ms | 0.47–0.76 s,帧 p95 75–1,008 ms |
|
||
| 首次 / 缓存命中切换会话 | 持平 | 持平 |
|
||
|
||
**更正(归因):** “改动后”构建包含了当时工作区中另一项未提交的改动——新会话面板分两步
|
||
渲染(先最后 20 条,首帧后补齐 80 条,后提交为 `09c415b`)。打开长会话的耗时下降主要来自
|
||
它,而不是流式缓冲;同时补齐那一帧变成长帧,4× 降速下该场景帧间隔 p95 从 9–42 ms 升到
|
||
75–1,008 ms。输入与流式期间输入的改善来自本步骤。分两步渲染只是过渡,补齐帧的成本需由
|
||
`PERF-14` 虚拟化解决。
|
||
|
||
不降速时(2 轮)各场景持平或略好,流式期间输入 p95 从 15–17 ms 降到 11–12 ms。流式本身在
|
||
4× 降速下仍每帧掉帧:剩余成本是行内 Markdown 解析与 KaTeX 渲染,以及每 250 ms 一次的 App
|
||
级吸收;切换会话的 App 级重渲染未改善,留给后续领域迁移。
|
||
- **远程 Runtime:** 远程事件与本地事件走同一 `agent:event` 通道和同一 Renderer 处理函数,
|
||
上述 Renderer 改进同样生效。远程事件在 Main 中逐条同步写 SQLite(每个 delta 约 2 个写事务,
|
||
会话恢复路径还会重写整条消息)后再转发,这部分未改,归入 `PERF-15`/`PERF-16`。
|
||
- **基准工具:** `GB_PERF_MODEL_PORT` 可把假模型移到其他端口(例如本机已运行 GoodBuddy 占用
|
||
11434 时),隔离 profile 通过 `GOODBUDDY_MODEL_*` 环境变量指向它。
|
||
|
||
#### 2026-10-02 其他改动的架构核对
|
||
|
||
对同期其他功能改动按本批次 5 条规则核对(监督者功能除外):
|
||
|
||
- **一致:** 剪贴板改为异步读写(`d773f5e`);未保存修改通过 context 上报、记录不进 App state,
|
||
有未保存修改的路由不被缓存淘汰(`43be465`);新会话面板分两步渲染(`09c415b`,过渡方案)。
|
||
- **偏离规则 1:** Story Graph 检索(`e97263c`)仍在 Main 内经 `DatabaseSync` 读取后,在 JS 中
|
||
逐条 `JSON.stringify` 并按多个关键词匹配,单次成本高于原整句匹配;工具描述又鼓励 Agent
|
||
主动调用,调用次数会增加。纳入 `PERF-15`/`PERF-16`,在此之前补测检索耗时并限制扫描规模。
|
||
- **偏离规则 2:** 侧栏 5 秒重排(`ee4eb73`,`use-conversation-list-order.ts`)由 App 持有状态,
|
||
渲染期间 setState,定时器每 5 秒检查;顺序不变时跳过,当前成本低。交互上悬停、滚动、菜单
|
||
打开时不重排。侧栏迁移和虚拟化(`PERF-13`/`PERF-14`)时移入侧栏组件。
|
||
- **React 规则:** 缓存会话保持滚动位置(`bf6c4a9`)在渲染期间写 ref,即当前 lint 的 3 条
|
||
`react-hooks/refs` 错误;启用 React Compiler 前需改为由状态派生顺序。
|
||
|
||
#### 2026-10-02 并行修复:补全分帧、远程批量写库、架构守护
|
||
|
||
- **补全分帧(`ChatHistoryPane.tsx`):** 新面板补全改为每帧 +20 条(20→40→60→80),每步后
|
||
贴底或按高度差保持阅读位置;“加载更早”、批次变化或笔记跳转到达时立即渲染完整批次。
|
||
- **渲染期 ref(`App.tsx`、`pane-order.ts`):** 会话面板顺序改为 state,用纯函数
|
||
`reconcilePaneOrder` 在渲染期按需调整,顺序不变时返回原数组;消除 3 条 `react-hooks/refs`。
|
||
- **远程批量写库(`remote-event-batcher.ts`、`assistant-database.ts`、`ipc.ts`):** 远程语义事件
|
||
按 checkpoint 一个事务提交(Agent 在 checkpoint 之后才可能 ACK),可恢复会话每批只读写一次
|
||
消息;非远程事件、`question`、`done`/`error`、显式 flush、256 条或 50 ms 计时器也会先提交。
|
||
只转发新写入的事件,顺序不变;提交失败整批回滚且之后拒绝继续提交。交互、后台/定时/通道
|
||
和重启恢复三条路径都已改用。当前 checkpoint 粒度是每条 transcript 记录而非每页,按页批量
|
||
需要改 `acp-remote-runtime.ts`。
|
||
- **架构守护(提前实施 `PERF-17` 的一部分):** `eslint.config.js` 新增只减不增的白名单规则:
|
||
`DatabaseSync`(19 个文件)、`src/main` 同步 fs(8 个文件)、组件直接订阅
|
||
`window.goodbuddy.*.on*`(6 个文件)、`App.tsx` `max-lines` 11700;文档演示和 vendor 脚本
|
||
不再参与 lint,`npx eslint .` 现为 0 错误。`build/perf-thresholds.json` +
|
||
`npm run perf:check`(或 `GB_PERF_CHECK=1 npm run perf:app`)检查不降速报告的阈值。
|
||
- **`npm run perf:app` A/B(改动前为 `9d8477b`):** 不降速 2 轮两版均通过 60 项阈值;打开长会话
|
||
从 1 个 Long Task / 56–61 ms、最大帧 50–58 ms 降到 0 个、最大帧 25–33 ms,其余持平。4× 降速
|
||
3 轮:打开长会话最大帧从 475–567 ms 降到 225–283 ms(帧 p95 因多出几帧小提交升到
|
||
175–258 ms);切换会话持平;流式场景两版方差都很大,未见一致变化。远程批量写库不在该基准
|
||
覆盖范围内,由单元测试和 `ipc.test.ts` 验证事务次数、顺序与回放去重。
|
||
|
||
#### 2026-10-03 会话与消息领域(第一片)
|
||
|
||
- **会话 store(`conversation-store.ts`、`conversation-selectors.ts`):** 会话数组移出 App state,
|
||
`getState()` 同步可读,取代 `conversationsRef` 及其 5 处直接写入;`set()` 先吸收流式 delta
|
||
再同步更新。按会话 id 和列表两级通知,选择结果未变时保持同一对象。
|
||
- **侧栏(`ConversationSidebar.tsx`):** 搜索匹配、5 秒重排、行级派生值移入侧栏自身订阅;
|
||
切换会话时侧栏只重渲染状态翻转的两行。顺便修复 React 19.2 中 `memo` 组件内
|
||
`useEffectEvent` 不刷新导致重排用旧列表的问题(有回归测试)。
|
||
- **App 只订阅:** 活动会话、缓存面板的会话、数量,以及标题/审批/项目活动的按值比较
|
||
选择器。`App.tsx` 从 11,636 行降到约 10,960 行。
|
||
- **仍在 App:** 未读、展开、菜单、重命名、删除等界面 ID;持久化、历史加载与合并;
|
||
活动会话的更新(含 250 ms 流式吸收)仍重渲染 App。
|
||
|
||
#### 2026-10-03 PERF-13 进展:Composer、路由边界、任务 store、切换会话
|
||
|
||
提交 `41be9f8`、`2e3d2e7`、`651475d`、`492f1d2`、`6b634c5`。
|
||
|
||
- **Composer(`Composer.tsx`,`memo`):** 输入区页脚(约 930 行 JSX、23 个内联回调)移出 App。
|
||
草稿读 `composer-draft-store`,会话字段读 `useComposerConversationView`(不含消息数和结果 ID),
|
||
弹出菜单状态放进 `composer-menu-store.ts`;动作经 `use-composer-actions.ts` 保持同一引用。
|
||
其他会话更新、当前会话新增消息、别的会话输入都不重渲染 Composer(有测试)。
|
||
- **路由边界:** `KeepAliveRoute` 改为 `memo`,隐藏路由在 App 重渲染时保持上次结果,再次显示时
|
||
补一次渲染。知识库、设置、监督、笔记、右侧栏、项目切换器改为 `memo`,知识库 26 个回调移到
|
||
`use-knowledge-workspace-actions.ts`,其余用 `useStableHandlers`;笔记面板元素 `useMemo`。
|
||
- **任务与结果(`task-store.ts` / `task-selectors.ts` / `task-sync.ts`):** `assistantTasks`、
|
||
`assistantArtifacts` 移出 App state,`assistantTasksRef` 删除。App 只选取顶层任务状态、活动摘要
|
||
所需字段和当前会话是否忙碌,子代理进度不再重渲染 App;结果刷新时内容相同的对象保留引用,
|
||
会话面板自己读结果表。`agent-event-handler.ts` 只改了任务/结果调用。队列状态未迁移:它和发送、
|
||
恢复、中断流程交织,单独拆风险高于收益。
|
||
- **切换会话:** 面板顺序改为渲染期派生(`usePaneOrder`),Composer 弹窗按上下文键关闭,
|
||
不再在渲染中 setState,切换一次只渲染 App 一次(测试在旧代码上为 2 次)。标题表在标题不变时
|
||
保持同一 Map。
|
||
- **规模:** `App.tsx` 9,576 → 7,904 行(含期间合入的监督者改动);App 内 useState 120→114、
|
||
useEffect 62→56、useRef 79→73、useCallback 89→83。lint 上限 9,600 → 8,000。
|
||
- **`npm run perf:app` A/B**(改动前为 `b28ddd4` 临时 worktree 构建,与 `6b634c5` 交替各 3 轮,
|
||
4× 降速,取中位数,括号为 3 轮范围):
|
||
|
||
| 场景 | 改动前 | 改动后 |
|
||
| --- | --- | --- |
|
||
| 首次切换会话 延迟 p95 | 525 ms(520–674) | 489 ms(470–607) |
|
||
| 首次切换 Long Tasks 数 / 总时长 | 37 / 3.9 s | 24 / 2.0 s |
|
||
| 缓存切换(warm)延迟 p95 | 254 ms(208–256) | 139 ms(135–149) |
|
||
| 缓存切换 Long Tasks 数 / 总时长 | 21 / 1.7 s | 12 / 0.7 s |
|
||
| 热切换(hot)延迟 p95 | 139 ms | 94 ms(77–154) |
|
||
| 流式 40 KB:Long Tasks 数 / 总时长 / 帧 p95 | 90 / 7.0 s / 208 ms | 49 / 3.1 s / 108 ms |
|
||
| 流式期间输入:延迟 p95 / Long Tasks 总时长 / 帧 p95 | 110 ms / 8.3 s / 233 ms | 53 ms / 3.5 s / 100 ms |
|
||
| 输入(有 300 个会话)延迟 p95 | 11.7 ms | 14.1 ms(12.6–15.9,噪声范围内) |
|
||
|
||
流式两个场景的掉帧率仍接近 100%(4× 降速下每帧仍有 Markdown 解析和 250 ms 吸收),但 Long
|
||
Tasks 减半。不降速一轮 `GB_PERF_CHECK=1`:60 项阈值全部通过。
|
||
|
||
### PERF-14 长列表虚拟化
|
||
|
||
#### 2026-10-03 消息时间线窗口化
|
||
|
||
- **实现(`chat-message-window.ts`、`ChatHistoryPane.tsx`、`ChatTimeline.tsx`):** 自研窗口化,
|
||
未引入依赖。只挂载视口附近的行(上限 60 行,外加获得焦点的行),其余用按实测高度或
|
||
平均高度估算的占位块代替;行高按会话缓存(最多 24 个会话)。以视口内的一行为锚点,
|
||
行高变化或挂载/卸载时修正滚动位置,读者看到的内容不动。
|
||
- **保留:** 96 px 贴底阈值和流式时每帧跟随;发送消息回到底部;“加载更早”仍按 80 条扩展
|
||
可滚动范围;笔记跳转先挂载目标再滚动;隐藏/显示和重新挂载恢复位置;隐藏面板不测量。
|
||
分帧补全(`09c415b`/`855b941`)已删除,打开面板一次提交有界行数。
|
||
- **滚动滞后窗口:** 视口离开已挂载范围 160 px 内才重新取窗,取窗时挂载 1.5 屏余量,避免
|
||
每个滚动事件都重挂行。
|
||
- **毛玻璃锚点:** 锚点以 `.chat-content` 的上内边距为基准,切换毛玻璃时阅读位置不变
|
||
(由 `tests/frosted-glass.electron.test.ts` 在真实 Electron 中验证,该测试改为按消息 id 跟踪
|
||
锚点行)。
|
||
- **已知限制:** 页内查找搜不到未挂载的消息;快速甩动时可能短暂看到空白占位;单条超长
|
||
消息仍整行挂载;估算高度会让滚动条滑块在首次经过时轻微变化。
|
||
|
||
### PERF-15/16 第一片:热点读路径移出 Main
|
||
|
||
#### 2026-10-03 只读查询 worker
|
||
|
||
- **实现(`readonly-query-reader.ts`、`readonly-query-worker.ts`):** 每个数据库文件一个只读
|
||
worker 线程,沿用 `execution-stats-reader` 的模式,复用 `KnowledgeDatabase` /
|
||
`AssistantDatabase` 的生产方法,结果与同步路径同源。最多 32 个并发请求;worker 崩溃或
|
||
启动失败时转为同步执行,30 秒后再重启;`:memory:` 始终同步。`AbortSignal` 通过共享标志
|
||
取消(向量扫描每 256 行检查)。没有新增 `DatabaseSync` 白名单。
|
||
- **已移出 Main:** 知识库检索(`retrieve`、`searchHybrid`);Story Graph 读取(Agent 通过 MCP
|
||
调用,先在 SQL 中按范围预筛,超过 5 万个对象返回 `scan_limit_exceeded`);会话搜索(一条
|
||
SQL + 注册函数,保持 `toLocaleLowerCase().includes()` 语义)。会话列表的 N+1 查询改为两次
|
||
批量查询,IPC 结构不变。
|
||
- **`npm run perf:knowledge`(改用 Electron 运行):**
|
||
|
||
| 文本块 | 混合检索时 Main 最大事件循环延迟(同步 → worker) | 单次耗时(同步 / worker) |
|
||
| --- | --- | --- |
|
||
| 5,000 | 54.9 → 19.5 ms | 47.7 / 53.3 ms |
|
||
| 20,000 | 204.3 → 19.6 ms | 188.6 / 198.9 ms |
|
||
| 50,000 | 489.9 → 18.6 ms | 466.8 / 478.6 ms |
|
||
|
||
- **剩余:** 向量检索仍是全表扫描(只是不在 Main 上);写入和其余约 530 处同步调用仍在
|
||
Main;独立数据进程和 `DatabaseSync` 白名单缩减未开始。
|
||
|
||
#### 2026-10-03 三项合并后的 `npm run perf:app` A/B(改动前为 `53b9ecb`)
|
||
|
||
不降速:新版本通过全部 60 项阈值。4× 降速(改动前 3 轮,改动后 3 轮,滚动场景另取调整后
|
||
2 轮):
|
||
|
||
| 场景 | 改动前 | 改动后 |
|
||
| --- | --- | --- |
|
||
| 首次切换会话 p50 / p95 | 335–377 / 677–730 ms | 198–312 / 518–634 ms |
|
||
| 切回刚看过的会话 p95 | 379–386 ms | 179–240 ms |
|
||
| 切回最近的会话 p95 | 161–164 ms | 89–140 ms |
|
||
| 打开 2,000 条消息会话 | 448–484 ms,帧 p95 200–225 ms | 290–354 ms,帧 p95 8–9 ms |
|
||
| 滚动长会话:帧 p95 / 掉帧 | 17 ms / 7% | 58–67 ms / 15–16% |
|
||
| 流式输出、流式时打字 | 两版波动都很大,未见一致变化 | 同左 |
|
||
| 打字、空闲 | 持平 | 持平 |
|
||
|
||
滚动是唯一变差的场景:改动前长会话一次性挂载所有已加载行,滚动只是移动画面;窗口化后
|
||
每隔几屏要挂载新行(含 Markdown 渲染)并测量。初版每个滚动事件都重新取窗,掉帧约 70%;
|
||
加入滞后窗口后降到 15–16%。剩余开销主要在新行渲染和 `getBoundingClientRect` 测量,后续
|
||
可把锚点测量合并到每帧一次,或给 Markdown 结果加缓存。
|
||
|
||
#### 2026-10-03 第二轮并行:滚动、Markdown 缓存、侧栏窗口化、App 继续瘦身、活动记录
|
||
|
||
- **滚动(`ChatHistoryPane.tsx`、`chat-message-window.ts`):** 滚动事件只读 `scrollTop` 等
|
||
滚动容器指标;是否重新取窗改为每帧最多判断一次,并用“锚点行 + 缓存行高”推算视口位置,
|
||
稳定滚动时不读元素几何。提交后只有挂载集合或占位高度变化时才校正锚点;上内边距缓存,
|
||
在 resize、激活和重新取锚点时刷新(真实 Electron 毛玻璃测试曾因缓存旧边距偏 105 px,已修)。
|
||
- **Markdown 渲染缓存(`markdown-render-cache.ts`):** 按源文本和管线配置缓存解析后的 hast
|
||
树(LRU),重新挂载的行跳过 micromark/remark/rehype/KaTeX,只做 hast → React。
|
||
- **侧栏窗口化(`use-conversation-list-window.ts`):** 60 个会话以上只挂载视口附近的行,
|
||
活动会话、菜单、重命名、获得焦点的行保持挂载;`aria-setsize`/`aria-posinset` 保留总数。
|
||
- **App 继续瘦身:** 会话持久化与历史加载(`conversation-persistence.ts`)、刷新调度
|
||
(`conversation-refresh.ts`)、Agent 事件处理(`agent-event-handler.ts`)、历史面板
|
||
(`ConversationHistorySlot.tsx`)移出 App。`App.tsx` 从约 10,960 行降到 9,566 行,lint 上限
|
||
改为 9,600。
|
||
- **活动记录(`activity-history-plan.ts`):** Main 的整份替换只校验、序列化变化的记录;
|
||
新增增量 IPC `activity-history:update`。Renderer 还没有改用增量接口。
|
||
- **会话读取:** `conversations:list-summaries` 和 `conversations:get` 也改由只读 worker 执行。
|
||
- **`npm run perf:app` 驱动:** 侧栏窗口化后不再一次挂载全部会话,驱动改为按 `aria-setsize`
|
||
判断加载完成,点击会话前逐屏滚动侧栏直到该行挂载。
|
||
|
||
`npm run perf:main`(1,000 个会话 / 2 万条消息 / 5,000 条活动记录,Main 事件循环最大延迟):
|
||
|
||
| 路径 | 改动前 | 改动后 |
|
||
| --- | --- | --- |
|
||
| 会话摘要列表 | 25.4 ms | 0.8 ms(单次耗时 31 ms,在 worker 上) |
|
||
| 活动记录整份替换,无变化 | 13.8 ms | 9.3 ms |
|
||
| 活动记录增量更新 1 条 | — | 5.3 ms |
|
||
|
||
`npm run perf:app`,4× 降速,改动前(`169941c`)与改动后各 3 轮:
|
||
|
||
| 场景 | 改动前 | 改动后 |
|
||
| --- | --- | --- |
|
||
| 滚动长会话:帧 p95 / 掉帧 | 25–50 ms / 10–11% | 17–41 ms / 9–10% |
|
||
| 打开 2,000 条消息会话:长任务 / 点击 p95 | 165–170 / 238–247 ms | 97–105 / 211–218 ms |
|
||
| 首次切换会话:长任务 / 点击 p95 | 1,969–2,115 / 444–460 ms | 1,256–1,341 / 425–435 ms |
|
||
| 切回刚看过的会话:长任务 / 点击 p95 | 1,084–1,183 / 151–158 ms | 152–205 / 110–146 ms |
|
||
| 流式输出 40 KB:长任务总时长 / 掉帧 | 1.6–2.4 s / 90–99% | 0.33–0.41 s / 63–72% |
|
||
| 流式时打字:长任务 / 按键 p95 | 3.0–3.6 s / 41–48 ms | 0.53–0.64 s / 29–43 ms |
|
||
| 有历史时打字:DOM 节点 | 4,382 | 705–719 |
|
||
|
||
不降速:全部阈值通过;流式输出和流式时打字的长任务均为 0。上一轮滚动的退化已消除(本轮基线
|
||
已含窗口化,故与 `53b9ecb` 的 17 ms / 7% 不完全可比,帧 p95 在两版中都波动较大)。
|
||
|
||
#### 2026-10-03 本期收尾:已做、暂缓与理由
|
||
|
||
本期收尾只做低风险改动:
|
||
|
||
- **侧栏 id 列表保持同一数组**(`ConversationSidebar.tsx`):流式更新时,会话对象每次都会变,
|
||
原来每次都生成新的 id 数组,导致侧栏窗口化的布局 effect 重跑并读取 `scrollTop`。4× 降速剖析
|
||
显示,这段在流式场景中占 562 ms self time。现在 id 不变时,数组也保持不变。
|
||
- **会话标题跑马灯不在挂载时强制布局**(`OverflowMarquee.tsx`):挂载时不再同步读取
|
||
`scrollWidth`,改由 ResizeObserver 在首次报告时测量,有回归测试。悬停时的测量方式不变。
|
||
- 4× 降速下复测,这两项的变化都在波动范围内(同版本多次运行,差距可达 2 倍),没有出现退化;
|
||
不降速时 60 项阈值全部通过。
|
||
|
||
**本期暂缓,理由如下:**
|
||
|
||
| 项目 | 理由 |
|
||
| --- | --- |
|
||
| Renderer 改用 `activity-history:update` | 需要在 App 中跟踪每次变化并处理顺序与失败重试;Main 端已经只处理变化的记录,Main 卡顿从 13.8 ms 降到 9.3 ms,剩余收益小,而出错会丢活动记录 |
|
||
| 数据库写入异步化、独立数据进程(PERF-15/16 剩余部分) | 涉及约 530 处调用和写入顺序语义,属于高风险大改动;本期已把最重的读取路径(检索、会话列表、搜索)移出 Main,需要单独立项 |
|
||
| 首次切换会话(4× 降速 p95 约 430 ms) | 剖析显示,开销分散在 React 渲染、首次 Markdown 解析和 i18n 上,没有单一热点;不降速时 p95 约 70 ms,已在阈值内 |
|
||
| 流式输出 Markdown 放进 worker | 需要重写渲染管线,并处理流式中途结果和公式;不降速时流式场景已无长任务 |
|
||
| 页内查找覆盖未挂载的消息 | 当前 App 没有页内查找功能(未调用 `findInPage`),属于新功能,不是回归 |
|
||
| 性能阈值接入 CI | 仓库目前没有 CI 工作流;`npm run perf:check` 可以在本地运行 |
|
||
|
||
### 7.2 按性能原则补齐差距(2026-10-03,第三轮并行)
|
||
|
||
统一标准见 `docs/architecture/performance-principles.md`(P1–P10、性能预算和评审清单)。本轮按其中“当前差距”并行推进:
|
||
|
||
| 原则 | 改动 | 测量结果 |
|
||
| --- | --- | --- |
|
||
| P1 | 文档解析、PPTX 提取、分块、大文件(≥ 1 MiB)哈希移到 `document-parse-worker` 线程,FIFO 队列;worker 不可用或崩溃时退回原来的 Main 内联执行,30 秒后重试;中止正在执行的任务会终止 worker。数据库写入仍由调用方按原顺序执行 | `npm run perf:document-parse`,各 3 轮:Main 最长阻塞 large.pdf 498–679 → 16–17 ms,large.docx 313–335 → 17–20 ms;总耗时基本不变 |
|
||
| P3 | 终端输出:Main 端按 8 ms 窗口合并(空闲时第一块立即发出,不增加回显延迟),单批不超过 64 KiB;控制事件(退出、错误、状态)发出前先清空缓冲,顺序不变;背压按“已发出 + 已缓冲”字节计算,暂停阈值留 1/4 余量。Renderer 端每帧最多写一次 xterm,写完后按最大序号累计 ACK;窗口不可见时 100 ms 定时兜底,保证 ACK 不中断 | 单元测试覆盖顺序、UTF-8 边界、背压、退出前清空;`perf:app` 未覆盖终端场景 |
|
||
| P5 | 笔记列表(按卡片行窗口化,CSS subgrid)、工作区文件树(展开后拍平,按缩进显示层级)、知识库文档表格只渲染可见部分;抽出通用 `use-list-window` / `use-grid-columns`,侧栏会话列表改用同一实现。选中项、打开菜单的行、焦点行保持挂载 | 每个列表新增窗口化单元测试;真实 Electron 文件树布局测试改为验证窗口化后的停靠、滚动和预览返回 |
|
||
| P6 | Main 启动打点(`startup-marks.ts`),新增 `npm run perf:startup`:首次运行 / 空配置 / 300 个会话三种配置,各 5 轮 | 见下表 |
|
||
| P9 | `package.json` 新增 `perf:startup`、`perf:document-parse` | — |
|
||
|
||
冷启动基线(标准机,中位数 / p95,ms):
|
||
|
||
| 阶段 | 首次运行 | 空配置 | 300 个会话 |
|
||
| --- | --- | --- | --- |
|
||
| 启动到输入框可用 | 1284 / 1322 | 1024 / 1040 | 1028 / 1040 |
|
||
| Main 模块加载(index.js 求值) | 414 / 420 | 411 / 420 | 413 / 423 |
|
||
| 本地工具环境初始化 | 154 / 169 | 160 / 163 | 153 / 160 |
|
||
| 知识库初始化 | 240 / 249 | 19 / 22 | 32 / 42 |
|
||
| 助手数据库初始化(同步) | 205 / 206 | 4 / 4 | 14 / 15 |
|
||
| Renderer 导航到输入框出现 | 228 / 233 | 226 / 244 | 224 / 228 |
|
||
|
||
启动的主要开销是 Main 模块加载(约 415 ms)和本地工具环境初始化(约 155 ms);首次运行另有约 450 ms 的建库开销。这些只做了测量,没有改动,后续按 P6 拆分延迟加载。
|
||
|
||
本轮回归:`perf:app` 60 项阈值全部通过(不降速,流式和流式打字长任务均为 0);typecheck、全量 lint 通过;全量测试中 2 个失败分别为 OpenCode 免费额度 403 的集成用例和 `magic-notes-*` 真实 Electron 测试在整套并行负载下的偶发失败,后者单独运行和 6 个 Electron 布局测试并行连跑 3 次均通过。
|
||
|
||
### 7.2.1 活动记录增量与分页(2026-10-04,P2/P4/P5)
|
||
|
||
之前启动时整份读取活动记录(本机约 2.4 万条、10 MB),任何变化 250 ms 后把整份列表 `replace` 回 Main;Main 每次在列表前面插入记录都要重写顺序 JSON。分三次提交改完:
|
||
|
||
1. **增量保存(`6aab78b`)。** 活动记录移出 App 状态,放进 `activity-store.ts`。领域动作(新增、更新请求、审批、按请求删除、按调用删除、结束请求、对账、清空)修改状态,同时排入对应的契约变更;`activity-sync.ts` 每 250 ms 用 `activityHistory.update` 发送(每次最多 2 万条),失败的变更按原顺序留在队列里,退避重试;退出和卸载前也会刷盘。清空改为新增的 `activityHistory.clear()`,Main 在一个事务里完成。
|
||
2. **Main 分页、汇总、对账(`352da66`)。** 第 57 版 schema 重建 `activity_history_records`:`order_seq`(越新越大,移到最前就分配新的最大值)加上从记录 JSON 生成的存储列(状态、类型、请求、会话、项目、创建时间)和索引。顺序从 `record_order_json` 原样迁移;旧数据里的重复 ID 保留,仍然只显示第一条。`ActivityHistoryRepository` 负责全部 SQL:按索引倒序分页 `page`,`summary` 返回面板原来从整份列表算出的计数、会话标题和会话状态,`reconcile` 用 SQL 关联任务表(追加的说明行由界面传入本地化文本),以及 `clear`。分页和汇总走只读 worker。
|
||
3. **界面只保留已加载的页(`bf9850a`)。** 启动顺序:发送排队的变更,Main 对账,再加载首页(200 条)。打开运行记录页时补足一批(500 条,和以前首屏一样多),“继续显示”先显示下一批,再在后台加载下一页;切换筛选会重新取首页;离开页面只留一批。计数、分组、标题、会话状态和清空数量都来自 Main 的汇总。没加载的记录也照样增量更新:`update` 新增按内容匹配的变更(`upsert-call`、`update-request`、`resolve-approval`、`settle-request`、`remove-request`、`remove-call`),由 Main 在 SQL 里执行,界面对已加载的页用同一份参考实现处理。任务刷新后的对账也改在 Main 里用 SQL 完成。旧 localStorage 记录改成增量变更迁移,界面不再调用 `replace`。
|
||
|
||
`npm run perf:main`,2.4 万条活动记录,两轮结果一致(括号内为 Main 事件循环最大延迟):
|
||
|
||
| 路径 | 之前 | 之后 |
|
||
| --- | --- | --- |
|
||
| 启动读取 | 整份 `get`:14.2 MB,178 ms(198 ms),在 Main 上执行 | 首页 + 汇总:114 KB,16 ms,在 worker 上执行(Main 0.6 ms) |
|
||
| 首页 200 条(同步路径,参考) | — | 1.0 ms(1.3 ms) |
|
||
| 汇总(同步路径,参考) | — | 14 ms(15.6 ms) |
|
||
| 保存 1 条变化 | 整份 `replace`:98 ms(114 ms) | `update`:3.9 ms(6.9 ms) |
|
||
| 每次保存的载荷 | 整份列表(约 14 MB) | 只含变化的记录,与总量无关(单元测试:3 条变化 < 1.5 KB) |
|
||
|
||
测试:随机动作序列(含翻页、截断、刷盘和注入的保存失败),在真实数据库上与改动前的整份列表逻辑逐步对比,最终数据库与整份 `replace` 的结果一致(含重复 ID 和移到最前);启动后界面不超过一页;保存载荷只取决于变化的记录(含未加载的);随机数据集(含重复 ID)下,分页加汇总渲染出的计数、分组、标题、状态和清空数量与整份列表渲染一致;重启后遗留的进行中记录被标为中断,说明文本相同;清空后数据库、界面和汇总都为空;SQL 变更语义与参考实现一致。`perf:app` 60 项阈值全部通过。
|
||
|
||
后续(2026-10-04):汇总覆盖全部已加载会话,超过 5,000 个时分多次请求再合并,不再截断(单元测试:5,003 个会话分 5,000 + 3 两次请求,全部有标题和状态)。分组里“会话活动数”统计的仍是已加载的记录,和以前统计已显示的记录一致。
|
||
|
||
### 7.2.2 Main 写库与启动:真实数据测量(2026-10-04,PERF-15)
|
||
|
||
用便携版 `dist/GoodBuddy-windows-x64/data` 的副本测量(源目录从不被打开):`assistant.sqlite` 2.4 GB,175.5 万条任务事件(其中 148 万条子任务进度、1.1 GB),4,812 条消息(metadata 约 400 MB,单条最大 4 MB),2.4 万条活动记录。新增 `npm run perf:write`(`build/run-write-perf.cjs`,按真实数据中最忙一秒的速率 270 条/秒回放真实事件),`perf:startup` 新增 `real` 变体(`GB_PERF_STARTUP_REAL_DATA`,每次启动用新副本,外网请求全部拦截)。
|
||
|
||
**测到的问题:**
|
||
1. 每次写库都按 `synchronous = FULL` 同步落盘,单次约 2.4 ms,与写入内容无关:子任务进度、工具、文本批、会话保存、活动记录都一样。
|
||
2. SQLite 自动 checkpoint 发生在越过 1,000 页的那次提交里:大库上每隔几百次写入就有一次卡住 Main 100–350 ms(3 轮里 18 次超过 50 ms)。
|
||
3. 启动时两条恢复语句、一条图片操作恢复语句各扫描全部消息的 metadata:在 Main 上同步约 2.3 秒,启动到输入框可用 3.7 秒。
|
||
|
||
**改动:**
|
||
- **频繁的本地写入改用 `synchronous = NORMAL`**:本地运行事件(生命周期事件 `status`/`done`/`error` 除外)、渲染进程 500 ms 一次的会话保存、活动记录增量。WAL 模式下仍是原子提交,应用崩溃不丢;断电最多丢最近一段这类写入,任何 FULL 提交及其之前的数据不受影响。远程事件(Agent ACK 依赖其持久性)、通道投递、任务状态等其余写入仍为 FULL。
|
||
- **WAL checkpoint 移到 worker 线程**:复用只读查询 worker 的入口,每 250 ms 做一次 PASSIVE checkpoint;运行期间 Main 的自动 checkpoint 阈值提到 4,000 页作为兜底(持续写入时被动 checkpoint 追不上 WAL 重置点),worker 退出即恢复默认 1,000。
|
||
- **启动恢复按索引读候选消息**:第 58 版 schema 新增部分索引 `messages_recovery_idx`(流式中,或 metadata 含未结束的工具、子任务、图片操作状态);没有用户队列项时跳过按 queueItemId 的全表删除。metadata 只由 `JSON.stringify` 写入,状态总以紧凑形式出现,精确判断仍在候选行上用原逻辑执行。一次性迁移在升级 worker 中执行,真实数据约 2 秒。
|
||
|
||
**结果(真实数据):**
|
||
|
||
| 指标 | 之前 | 之后 |
|
||
| --- | --- | --- |
|
||
| 单次写库 p50(子任务 / 工具 / 文本 / 会话保存 / 活动记录) | 2.2–2.6 ms | 0.1–0.3 ms |
|
||
| 写入期间 Main 最长阻塞(270 条/秒,3 轮) | 8–346 ms,超过 50 ms 共 37 次 | 2–34 ms,超过 50 ms 0 次 |
|
||
| 启动:Main 上的初始化(`assistant-db-init`) | 2,564 ms | 232 ms |
|
||
| 启动:到输入框可用(中位数,4 次) | 3,702 ms | 1,359 ms |
|
||
| 启动:侧栏出现会话 | 4,081 ms | 1,749 ms |
|
||
| 启动期间 Main 最长阻塞 | 2.5–2.8 s | 236–257 ms |
|
||
|
||
部分索引的代价:metadata 更新时如果新旧值都命中候选条件,要多维护一次索引(4 MB metadata 的更新 36 → 59 ms,233 KB 的 0.7 → 1.6 ms);不命中时没有额外开销。启动时剩下的约 0.6 s 在本地工具环境探测(已在后台初始化,不阻塞 Main)。
|
||
|
||
未做:读取最大的会话(407 条消息、42 MB metadata)在 worker 上约 170 ms,不阻塞 Main,但切换到这个会话时要等这么久;需要按需加载 metadata,单独立项。
|
||
|
||
### 7.3 体验倒退复查(2026-10-03)
|
||
|
||
逐一复查了所有性能提交对交互体验的影响,并新增原则 P10“体验不倒退”。发现并修复:
|
||
|
||
| 问题 | 来源 | 修复 | 验证 |
|
||
| --- | --- | --- | --- |
|
||
| “到底部”按钮的平滑滚动动画消失,直接跳到底部 | `fdf7d83` 消息窗口化:贴底跟随、ResizeObserver、锚定修正都会瞬间写入 `scrollTop`,打断平滑滚动;点击时还立即切换窗口导致可见行被卸载 | 滑动期间暂停所有滚动写入,只随视口重新窗口化;到达底部、`scrollend` 或 1.5 秒兜底时再贴底;用户滚轮、按键或点击会接管滚动 | 真实 Electron 逐帧采样,要求有中间帧(旧代码中间帧为 0),连跑 3 次通过;新增单元测试,旧代码失败 |
|
||
| 消息滚出可见区后丢失状态:待回答问题里已选的选项和输入的内容、展开的工具详情和推理、Mermaid 重新绘制并显示“加载中” | `fdf7d83` | 有待回答问题或待审批的行、用户交互过的最近 8 行、选区两端所在的行保持挂载;Mermaid 按“主题 + 源码”缓存渲染结果(最多 64 个) | 单元测试,旧代码失败 |
|
||
| 跨多条消息选择文本时,选区起点被卸载;复制时只拿到已挂载的文字 | `fdf7d83` | 选区两端所在的行保持挂载;复制时,未挂载的消息用原文补全 | 单元测试 |
|
||
| 侧栏在上方插入、删除、置顶或 5 秒重排时,可见的会话行跳动 | `663aa85`,窗口化关闭了原生滚动锚定 | 会话 id 变化时,在绘制前按第一条可见行恢复位置;列表在顶部时不锚定 | 单元测试,旧代码失败 |
|
||
| 笔记、文件树、知识库文档表格的锚定修正可能和浏览器原生锚定冲突 | `b2eb86a` | 窗口化状态下关闭 `overflow-anchor` | 单元测试 |
|
||
| 终端打字回显多等一帧;点击“清屏”前到达的输出在清屏之后才显示 | `f4458af` | 空闲时立即写入,只合并同一帧内后续到达的输出;清屏前先写完待写入的输出 | 单元测试 |
|
||
| 远程 Runtime 流式输出最多 50 ms 一批,显示一顿一顿 | `d859cbf` | 合并间隔改为 16 ms;写库成功后立即转发,仍然是先写库再转发 | 单元测试 |
|
||
| 流式输出时从侧栏复制或导出会话,可能缺最后不超过 250 ms 的文字 | PERF-13 流式缓冲 | 复制、导出前先把待同步的流式文字刷进状态 | 单元测试,旧代码失败 |
|
||
|
||
检查后没有问题:CSS 动画和过渡没有被删改;其余 `scrollIntoView` 的行为和原来一致;标题跑马灯、侧栏 5 秒重排的悬停冻结、重命名和菜单行保持挂载;输入框自动增高;Markdown 和 KaTeX 渲染结果。快速大跨度跳转是否出现空白帧也做了实测:在 `perf:app` 滚动场景中加了检查,CPU 降速 4 倍时 10 次跳转中空白帧为 0,所以没有改动。
|
||
|
||
已知限制(不是倒退,原来也没有):读屏软件的浏览模式访问不到未挂载的消息;应用本身没有页内查找功能。
|
||
|
||
修复记录:文件树窗口化最初以 `.assistant-sidebar__body` 为滚动容器,但真实布局中滚动的是停靠的 `.workspace-files__files-view`,真实 Electron 测试发现后改为优先使用后者。
|
||
|
||
### PERF-14 状态
|
||
|
||
- **优先级 / 状态:** P2 / 进行中(消息时间线和侧栏已窗口化;页内查找等限制未解决)
|
||
- **实施条件:** `PERF-13` 完成会话与消息迁移,行组件 props 已稳定。
|
||
- **范围:** 消息时间线和会话侧栏优先;保留滚动位置、跳转、展开状态和“加载更早消息”语义。
|
||
替换新面板分两步渲染(`09c415b`);侧栏 5 秒重排状态随侧栏迁出 App。
|
||
- **验收:** 5,000 条消息和 1,000 个会话场景下 DOM 节点数有界,滚动与切换指标改善。
|
||
|
||
### PERF-15 存储接口异步化
|
||
|
||
- **优先级 / 状态:** P1 / 进行中(热点读走 worker;高频写降为 NORMAL、checkpoint 移出 Main、启动恢复按索引,见 7.2.2;接口异步化未开始)
|
||
- **范围:** 在原进程内把 `AssistantDatabase`、`KnowledgeDatabase` 等对外接口改为异步,
|
||
调用方全部 `await`;会话列表只返回摘要,搜索下推到 SQL,活动记录改为增量追加。
|
||
远程 Runtime 实时事件改用已有的 `appendRemoteTaskEventsBatch` 批量写入,不再逐条同步事务。
|
||
Story Graph 检索先补测耗时,扫描规模设上限,匹配尽量下推到 SQL。
|
||
- **验收:** 行为与测试不变;`PERF-11` Main event-loop 延迟不变差。
|
||
|
||
### PERF-16 数据进程
|
||
|
||
- **优先级 / 状态:** P1 / 待开始
|
||
- **实施条件:** `PERF-15` 完成,调用方已全部异步。
|
||
- **范围:** 新增独占 SQLite 的 `utilityProcess`,承接会话、知识库、向量检索、Story Graph
|
||
(含 Agent 主动调用的检索)、远程 Runtime 事件持久化、活动记录及文档解析;Main 只转发;
|
||
保证同一时刻只有一个写入方。吸收 `PERF-06` 的范围。
|
||
- **验收:** 大知识库检索、会话列表和文档导入期间 Main event-loop 延迟达到批次目标;取消、
|
||
崩溃恢复和迁移语义不回退。
|
||
|
||
### PERF-17 架构守护
|
||
|
||
- **优先级 / 状态:** P2 / 待开始
|
||
- **范围:** lint 禁止 `src/main` 使用同步文件 API 和在数据进程外引用 `DatabaseSync`;禁止
|
||
组件直接订阅 `window.goodbuddy.*.on*`;限制 `App.tsx` 规模;`PERF-11` 关键指标设置回归阈值。
|
||
- **验收:** 违反规则的改动在 lint 或基准阶段失败。
|
||
|
||
## 8. 体验与可访问性
|
||
|
||
### UX-01 统一 Ask/Execute 文案
|
||
|
||
- **优先级 / 状态:** P1 / 已完成
|
||
- **范围:**
|
||
- 删除“Execute 高风险操作仍需审批”的冲突说明。
|
||
- 统一表达为使用当前账号完整权限执行,调用记录到活动。
|
||
- 同步定时任务、Composer、Runtime、帮助文案及中英文资源。
|
||
- **验收:** 所有入口对 Ask/Execute 的描述与真实 Runtime 契约一致。
|
||
|
||
### UX-02 Runtime 显式状态
|
||
|
||
- **优先级 / 状态:** P1 / 已完成
|
||
- **范围:** 顶栏以状态点和 Runtime/模型名称紧凑表达状态:就绪为绿色,连接中为黄色,
|
||
不可用为灰色;不重复显示状态文字,并通过 `aria-describedby` 暴露完整原因。
|
||
- **验收:** 三种状态均不重复显示状态文字;发送不可用原因仍由发送控件的可访问说明提供。
|
||
|
||
### UX-03 等待审批实时通知
|
||
|
||
- **优先级 / 状态:** P1 / 已完成
|
||
- **范围:** 审批卡首次出现时用 polite live region 播报等待状态和工具名;不抢焦点。
|
||
- **验收:** 屏幕阅读器能发现新审批,右侧栏数量变化也有可访问状态。
|
||
|
||
### UX-04 长任务阶段播报
|
||
|
||
- **优先级 / 状态:** P1 / 已完成
|
||
- **范围:** 低频阶段使用 `role="status"`,失败终态使用 `role="alert"`,不逐字播报正文。
|
||
- **验收:** 准备、等待、完成和失败可被辅助技术理解,流式输出不会形成播报风暴。
|
||
|
||
### UX-05 Composer 错误关联
|
||
|
||
- **优先级 / 状态:** P1 / 已完成
|
||
- **范围:** 附件和上下文错误使用稳定 ID、`role="alert"`、`aria-describedby` 和适当的
|
||
`aria-invalid`。
|
||
- **验收:** 键盘和屏幕阅读器用户能知道发送失败原因及对应操作区域。
|
||
|
||
### UX-06 设置离开确认焦点
|
||
|
||
- **优先级 / 状态:** P1 / 已完成
|
||
- **范围:** 确认出现时滚动到可见区域并聚焦“继续编辑”,或改用共享确认对话框。
|
||
- **验收:** 在长设置页底部切换分类、关闭设置和应用导航时均不会出现不可见确认。
|
||
|
||
### UX-07 SSH Host 读取失败状态
|
||
|
||
- **优先级 / 状态:** P1 / 已完成
|
||
- **范围:** 读取失败与真实空列表分开,失败状态提供重新读取。
|
||
- **验收:** 暂时读取失败不会诱导用户重复添加 Host,重试成功后恢复原列表。
|
||
|
||
### UX-08 工作区预览重试
|
||
|
||
- **优先级 / 状态:** P2 / 已完成
|
||
- **范围:** 加载使用 `role="status"` 和 `aria-busy`;失败保留路径并提供重新读取。
|
||
- **验收:** 网络抖动后无需返回目录重新查找文件即可恢复预览。
|
||
|
||
### UX-09 最近对话空状态
|
||
|
||
- **优先级 / 状态:** P2 / 已完成
|
||
- **范围:** 区分首次为空、搜索无结果和加载失败,分别提供新建、清除搜索和重试。
|
||
- **验收:** 三种状态文案和操作互不混用。
|
||
|
||
### UX-10 共享加载状态语义
|
||
|
||
- **优先级 / 状态:** P2 / 已完成
|
||
- **范围:** 为共享 `EmptyState` 增加 loading 变体,并迁移知识库、智能心跳和 SSH Host。
|
||
- **验收:** 加载状态统一输出 `role="status"`、`aria-live="polite"` 和 `aria-busy="true"`。
|
||
|
||
### UX-11 更新检查错误反馈
|
||
|
||
- **优先级 / 状态:** P1 / 已完成
|
||
- **范围:**
|
||
- 启动检查失败进入去重后的全局错误通知。
|
||
- 保留来源、网络和有界 HTTP 错误上下文。
|
||
- 打开下载页失败也显示通知。
|
||
- **验收:** 不再以空 `catch` 静默丢失用户可处理的更新错误。
|
||
|
||
### UX-12 更新源与自动检查解耦
|
||
|
||
- **优先级 / 状态:** P2 / 已完成
|
||
- **范围:**
|
||
- 关闭启动检查后仍可选择 GitHub 或镜像。
|
||
- 手动检查和下载继续使用所选来源。
|
||
- 同步修订 `UI-DESIGN.md` 当前相反的规则。
|
||
- **验收:** “关闭自动检查、切换镜像、手动检查”流程通过测试。
|
||
|
||
## 9. 诊断与质量
|
||
|
||
### OPS-01 桌面端有界诊断日志
|
||
|
||
- **优先级 / 状态:** P1 / 已完成
|
||
- **范围:** 轮转记录最近启动和 Runtime 失败的时间、组件、阶段和稳定错误码。
|
||
- **限制:** 不记录 Prompt、凭据、文档正文或 Provider 原始响应。
|
||
- **验收:** 重启后仍可定位最近一次人为制造的数据库或 Runtime 启动失败。
|
||
|
||
### OPS-02 远端 Agent 有界诊断日志
|
||
|
||
- **优先级 / 状态:** P1 / 已完成
|
||
- **范围:** Detached Agent 保存有界轮转诊断,覆盖 Runtime 启动、退出、连接和恢复阶段。
|
||
- **验收:** 人为制造远端失败后可导出根因阶段;关闭和轮转不会遗留无界文件。
|
||
- **限制:** 不记录 accepted Prompt、模型密钥或完整模型配置。
|
||
|
||
### OPS-03 反馈可选附加诊断
|
||
|
||
- **优先级 / 状态:** P1 / 已完成
|
||
- **范围:** 用户主动勾选后才附加诊断,提交前说明类别,保留现有截图和草稿重试。
|
||
- **验收:** 默认反馈不附加诊断;勾选后只包含允许的有界记录。
|
||
- **依赖:** `OPS-01`;本批明确不自动读取或附加 `OPS-02` 的远端 Agent 记录,
|
||
远端记录通过 Agent 固定 CLI 独立导出。
|
||
|
||
### QA-01 真实 Runtime E2E
|
||
|
||
- **优先级 / 状态:** P1 / 待开始
|
||
- **范围:**
|
||
- 直连模型一次有界真实请求。
|
||
- Bundled OpenCode 启动并执行文件任务。
|
||
- Continue 启动并完成一次任务。
|
||
- MCP 工具写入生产路径。
|
||
- **验收:** 受保护发布候选任务运行通过,并记录精确模型调用次数和费用边界。
|
||
|
||
### QA-02 本地兼容 Provider E2E
|
||
|
||
- **优先级 / 状态:** P1 / 待开始
|
||
- **目标:** 常规 CI 无需公网或真实模型凭据也能覆盖打包 Runtime 启动和工具路径。
|
||
- **范围:** 提供最小本地 OpenAI 兼容测试 Provider,覆盖流式响应和工具调用。
|
||
- **验收:** SDK、Runtime 二进制、Provider 适配或工具写入回归会使常规 CI 失败。
|
||
|
||
### QA-03 Windows ARM64 覆盖纠正
|
||
|
||
- **优先级 / 状态:** P1 / 待开始
|
||
- **范围:**
|
||
- 文档明确 Windows ARM64 当前为交叉打包。
|
||
- 在真实设备验证 Electron、node-pty、Koffi、Canvas、OCR 和 Runtime 加载。
|
||
- 有可用 ARM64 Runner 后再声明原生 CI。
|
||
- **验收:** 文档和发布声明不再把文件架构验证描述为原生运行验证。
|
||
- **外部依赖:** 真实 Windows ARM64 设备或 Runner。
|
||
|
||
## 10. 每批完成标准
|
||
|
||
一个事项只有同时满足以下条件才可标记为“已完成”:
|
||
|
||
1. 正常 UI 操作到达生产实现并产生预期结果,不以 mock、注入 fake 或禁用入口代替。
|
||
2. 已覆盖本事项声明的可达失败场景,用户输入和可恢复上下文不会被静默丢弃。
|
||
3. 新增或更新聚焦测试;功能变化后运行:
|
||
|
||
```text
|
||
npm test
|
||
npm run typecheck
|
||
npm run lint
|
||
```
|
||
|
||
4. 生产构建或发布脚本变化后运行 `npm run build`。
|
||
5. 改动涉及 GoodBuddy Agent 或 Desktop-to-Agent 生产路径时,按 `AGENTS.md` 在共享 Linux x64
|
||
Test Host 验证当前源码或候选包;Host 不可达时明确记录阻塞,不能标记完成。
|
||
6. 检查并更新受影响的 PRD、架构、设计、功能矩阵、构建和运维文档。
|
||
7. 在批次记录中填写实际验证命令、结果、剩余限制和对应提交。
|
||
|
||
## 11. 批次记录模板
|
||
|
||
开始一个批次时,在本节末尾复制以下模板:
|
||
|
||
```markdown
|
||
### 批次 N:标题
|
||
|
||
- 状态:进行中
|
||
- 范围:ITEM-01、ITEM-02
|
||
- 明确不包含:
|
||
- 开始日期:
|
||
- 完成日期:
|
||
- 外部依赖:
|
||
|
||
#### 验收结果
|
||
|
||
- [ ] 正常生产路径
|
||
- [ ] 声明的失败与恢复路径
|
||
- [ ] 聚焦测试
|
||
- [ ] npm test
|
||
- [ ] npm run typecheck
|
||
- [ ] npm run lint
|
||
- [ ] npm run build(如适用)
|
||
- [ ] 真实 Host 或目标平台验证(如适用)
|
||
- [ ] 相关文档更新
|
||
|
||
#### 结果与剩余限制
|
||
|
||
- 提交:
|
||
- 验证:
|
||
- 剩余限制:
|
||
```
|
||
|
||
### 批次 1A:体验一致性与 P1 诊断
|
||
|
||
- 状态:已完成
|
||
- 范围:`UX-01` 至 `UX-12`、`OPS-01` 至 `OPS-03`
|
||
- 明确不包含:`REL-*`、`FUN-*`、`PERF-*`、`QA-*`,以及初始 Renderer Bundle 优化
|
||
- 开始日期:2026-08-31
|
||
- 完成日期:2026-08-31
|
||
- 外部依赖:无
|
||
|
||
#### 验收结果
|
||
|
||
- [x] 正常生产路径
|
||
- [x] 声明的失败与恢复路径
|
||
- [x] 聚焦测试
|
||
- [x] `npm test`
|
||
- [x] `npm run typecheck`
|
||
- [x] `npm run lint`
|
||
- [x] `npm run build`
|
||
- [x] 共享 Linux x64 Host 的当前源码 Agent 候选验证
|
||
- [x] 相关文档更新
|
||
|
||
#### 结果与剩余限制
|
||
|
||
- 提交:尚未提交
|
||
- 验证:306 个测试文件、3322 项测试通过,49 项按既有条件跳过;Node/Web TypeScript、
|
||
完整 ESLint、生产构建和 Renderer Bundle 预算均通过。共享 Linux x64 Host 使用隔离
|
||
installation 完成 bootstrap、attach、断开重连、controller resume、Runtime 进程启动、
|
||
CLI 诊断导出、重启读取、权限和诊断写失败隔离;真实文本模型调用 0 次。
|
||
- 剩余限制:真实 Host 本次没有通过 Agent-owned model bridge 执行 Prompt,因此未在该
|
||
Host 上覆盖 Prompt 期间的 `runtime.starting/runtime.started` 诊断;三文件真实轮转仍由
|
||
聚焦测试覆盖。正式发布、功能增强、性能基线和 QA 路线均不属于本批。
|