Files
sorrow404null bc0b92bcdc docs: add agent handoff docs, verification guide, and repair evidence
Add docs/agent-handoff (backlog, execution state, verification, platform plan, per-task evidence), repo AGENTS.md, and architecture notes updated for the dual-renderer design.
2026-09-17 16:40:06 +08:00

549 lines
32 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 修复任务清单
本文是修复工作的任务源。每次只领取一个任务单元;任务完成后再进入下一个单元。精确实现以当前源码为准,本文约束修复范围和验收结果,不授权功能扩展。
验证方法与平台取证要求统一见 [VERIFICATION.md](VERIFICATION.md)。跨平台运行契约见 [PLATFORM_PLAN.md](PLATFORM_PLAN.md)。
## 执行边界
- 当前阶段只保证源码运行。安装包、冻结构建、AppData/XDG 目录迁移均不在本清单内。
- 保持现有模块位置,不拆分或移动 `ui/views/main_window.py``ui/web/app.js` 等大文件。可以新增测试、窄的平台适配器和配置路径辅助模块。
- 保持现有 Agent 能力和 `core/agent/` 与 pi 的行为,不增加工具注册、权限确认、命令沙盒或路径边界。
- 保留 Chromium sandbox。只有明确检测到 root/container 且用户显式选择时,才允许添加 `--no-sandbox`
- `data/config.json` 是不透明的运行时秘密。工作 Agent 不得打开、读取、搜索、打印、复制或修改该文件;所有自动化测试使用临时配置。
- 第三方修复文档只是线索。只有源码可复现的缺陷和本清单明确写出的行为才是修复依据。
- 当前快照没有 Git 历史。每个任务仍须保持可独立审查;以后接入 Git 时,一个任务对应一个提交。
## 顺序与依赖
| 顺序 | 任务 | 状态 | 依赖 |
|---|---|---|---|
| P0-01 | 配置路径与测试隔离 | 已完成(P0 阶段回归通过,2026-09-16;见 evidence/P0-01.md | 无,其他 GUI/配置测试的前置任务 |
| P0-02 | 合并重复的 `MainWindow.eventFilter` | 已完成(P0 阶段回归通过,2026-09-16;见 evidence/P0-02.md | P0-01 |
| P0-03 | 修复交接文档基线 | 已完成(2026-09-16) | 无;后续只做一致性复核 |
| P1-01 | 双向消息渲染窗口 | 已完成 | P0-01 |
| P1-02 | Windows/Linux shell 与进程树终止 | 已完成(Windows 侧自动化全绿,2026-07-09;见 evidence/P1-02.md | P0-01 |
| P1-03 | Windows/Linux 渲染器启动链 | 已完成(Windows 侧自动化 + offscreen 真实启动链全绿,2026-07-17;见 evidence/P1-03.md | P0-01、P1-02 |
| P1-04 | Linux 截图热键与截图实现 | 已完成(Windows 侧自动化全绿,2026-07-17X11/Wayland 真机待手动;见 evidence/P1-04.md | P1-03 |
| P2-01 | Bash 任务按启动时间倒序 | 已完成(offscreen 全量覆盖,2026-07-21;见 evidence/P2-01.md | P0-01 |
| P2-02 | 右侧 Bash 面板滚动条 | 已完成(offscreen 全量覆盖,2026-07-21;真机截图待对应环境跑 diag;见 evidence/P2-02.md | P2-01 |
| P2-03 | WebView2 原生窗口遮挡层 | 已完成(offscreen 34 项全绿,2026-07-21;真机 WebView2 人工验收清单见 evidence/P2-03.md | P0-02、P1-03 |
| P2-04 | 跨平台聚合测试入口 | 已完成(2026-07-17 | 新增 `tests/run_all.py`Windows 25/25 PASSWSL/3.12 FAIL 0 + 21 有理由 SKIP;暴露并修复 T9 陈旧断言与 `core/agent/compaction.py` 的 3.11+ dataclass 缺陷;证据 `evidence/P2-04.md` |
## P0-01 配置路径与测试隔离
**状态:已实施(定向测试通过)。** 新增 `core/config_paths.py`(统一入口,环境变量优先、容错加载、可见警告);`core/llm_engine.py``ui/views/main_window.py`webview_backend 与 init_model_popup 两处)、`ui/views/bash_panel.py` 全部改走统一入口;新增 `tests/_test_env.py` 统一临时环境与 `tests/test_config_isolation.py` 隔离回归。证据见 [evidence/P0-01.md](evidence/P0-01.md)。
**目标**
让所有配置读取方统一尊重 `HAOCODE_CONFIG_FILE`,并保证自动化测试在导入 GUI 前完成数据库和配置重定向。默认源码运行仍使用项目内 `data/`
**先读文件**
- `core/llm_engine.py`
- `ui/views/bash_panel.py`
- `ui/views/main_window.py` 中所有配置路径和配置读取函数
- `core/db_manager.py``_DEFAULT_DB` 的定义和初始化时机
- `tests/smoke_bash_panel.py`
- `tests/test_error_persist.py`
- `tests/test_agent_core.py` 中依赖 provider 配置的用例
只读源码中的路径引用,不读取 `data/config.json` 的内容。
**允许修改**
- 上述源码和测试。
- 可以新增一个只负责路径解析和容错加载的 `core/` 辅助模块,以及一个 `tests/` 临时环境辅助模块。
- 可以新增 `tests/test_config_isolation.py`
**硬约束**
- 环境变量优先级统一;没有环境变量时才回到项目内现有路径。
- 路径解析本身不得在 import 时输出、复制或迁移配置内容。
- 缺失或格式错误的配置必须产生可见警告,并使用现有安全默认值继续启动;不得吞掉错误,也不得因此阻断不需要该配置的源码路径。
- 测试必须先创建临时配置、设置 `HAOCODE_CONFIG_FILE`、重定向 `core.db_manager._DEFAULT_DB`,然后才能 import `MainWindow`
- 不修改真实配置,不引入配置迁移或新配置格式。
**目标测试**
```text
python tests/test_config_isolation.py
python tests/test_error_persist.py
python tests/smoke_bash_panel.py
python tests/test_agent_core.py
```
**完成证据**
- 测试用拦截器记录到的配置打开路径全部位于临时目录,且禁止路径从未被打开;此断言不得通过读取或散列真实配置完成。
- 临时配置的读写用例通过,临时数据库之外没有数据库写入。
- 缺失配置和损坏配置各有一个回归用例,日志包含明确警告,进程正常退出。
- 源码中不存在绕过统一路径解析的运行时配置读取。
## P0-02 合并重复的 `MainWindow.eventFilter`
**状态:已实施(定向测试通过)。** 两处定义合并为一份(位于 `init_chat_events` 前的「事件拦截」节);发送规则(按钮禁用 / 流式生成时 Enter 不发送)收进 `send_message(from_enter=...)` 单一实现,按钮点击的中断语义不变。证据见 [evidence/P0-02.md](evidence/P0-02.md)。
**目标**
修复同一类中后定义方法覆盖前定义方法的确定性缺陷,使 Enter、Shift+Enter、发送按钮状态和流式生成状态使用一套事件策略。
**先读文件**
- `ui/views/main_window.py``MainWindow.init_chat_events`、两处 `MainWindow.eventFilter``send_message``_update_send_button_state`
- 与输入框发送行为相关的现有 smoke 测试
**允许修改**
- `ui/views/main_window.py` 的事件过滤逻辑。
- 可以新增 `tests/test_main_window_event_filter.py` 或在现有离屏 smoke 中增加断言。
**硬约束**
- `MainWindow` 最终只能有一个 `eventFilter` 定义。
- Enter 只发送一次;Shift+Enter 放行换行;发送按钮禁用或当前会话正在流式生成时不得发送。
- 其他对象和事件必须继续交给父类,不顺带重构主窗口事件系统。
- 测试遵守 P0-01 的临时配置和临时数据库规则。
**目标测试**
```text
python tests/test_main_window_event_filter.py
python tests/smoke_offscreen.py
python tests/smoke_mode.py
```
**完成证据**
- 四种键盘状态均有断言:Enter 可发送、Enter 被禁用、流式时 Enter 被拦截、Shift+Enter 换行。
- 静态断言或 AST 检查证明 `MainWindow` 只有一个 `eventFilter`
- 测试中一次按键对应最多一次 `send_message` 调用。
## P0-03 修复交接文档基线
**状态:已完成。** 2026-09-16 无人值守轮次按本节标准复核:三项 `rg` 回归通过,交接目录与 `AGENTS.md` 相对链接全部有效;并随 P0-01/P0-02 结果更新了 README/CURRENT_STATE/VERIFICATION 中相应过时表述。保留本节作为后续文档变更的回归标准,不要重复搬运或重建交接目录。
**目标**
让后续 Agent 只从一条清晰入口读取当前事实,并把损坏或过时材料降级为历史资料。
**先读文件**
- `AGENTS.md`
- `ARCHITECTURE.md`
- `docs/agent-handoff/README.md`
- `docs/agent-handoff/CURRENT_STATE.md`
- `docs/agent-handoff/PLATFORM_PLAN.md`
- 本文和 `VERIFICATION.md`
**允许修改**
- 仅上述文档。
**硬约束**
- `AGENTS.md` 的入口指针必须覆盖修复、跨平台、测试、结构审查和工作交接五类触发场景。
- `ARCHITECTURE.md` 必须移除被拼接进来的第三方任务书正文,并在旧架构内容前明确标记“历史资料”;不得把旧目录树继续描述为当前事实。
- 当前支持矩阵、任务要求和验证规则分别只有一个权威位置,通过链接引用,不复制成多份。
- 不创建虚构的 Git 历史,不声称缺失的脚本或打包配置已经存在。
**目标测试**
```text
rg -n "agent-handoff/README.md" AGENTS.md
rg -n "历史资料|当前事实" ARCHITECTURE.md
rg -n "haocode 修复需求(团队任务书)" ARCHITECTURE.md
```
最后一条应无匹配;再逐一检查交接文档中的相对链接和文件路径存在性。
**完成证据**
- 新 Agent 按 `AGENTS.md` 指针能在一次跳转内到达 `docs/agent-handoff/README.md`
- `ARCHITECTURE.md` 没有拼接残片,且任何保留旧内容均带历史标记。
- 交接目录中没有互相冲突的支持矩阵、默认值或验收口径。
## P1-01 双向消息渲染窗口
**目标**
把前端 DOM 限制为严格的双向滑动窗口,同时保留完整当前会话链、附件、时间线、工具输出、分支切换和流式体验。
**先读文件**
- `ui/views/main_window.py``load_messages_to_web`、分支切换、删除/重答和 `_active_streams`
- `ui/views/chat_bridge.py`
- `ui/web/app.js` 中消息创建、`messageBuffer`、滚动、`clearChat`、流式完成和时间线回放
- `ui/web/index.html``ui/web/style.css`
- `core/db_manager.py``get_message_chain` 和分支查询
- `tests/test_math_extract.js``tests/smoke_timeline.py``tests/smoke_midswitch.py`
**允许修改**
- 上述 Python/JS/CSS 文件和相关测试。
- 可以新增一个 DOM 无关的 JS 窗口状态模块、`tests/test_render_window.js``tests/diag_render_scale.py`
- 可以在统一配置加载器中增加 `render_window_mode``render_window_size` 的解析。
**硬约束**
- Python 保留完整的当前会话可见消息链作为窗口数据源;本任务不改 SQLite 查询模型或数据库 schema。
- 初始窗口为最新 `size` 条。`size` 只接受非布尔整数 `10..200`,缺失、布尔、字符串、零、负数和越界值都静默回落 `40`。模式只接受 `auto`/`manual`,否则回落 `auto`
- `.message-wrapper` 数量始终不超过 `size`。流式消息计入上限;默认值下有一条流式消息时,最多保留另外 39 条。
- JS 以“方向 + 边界消息 ID”向 Python 请求页;Python 以单个批次返回完整消息描述。描述必须足以独立还原正文、reasoning、附件、时间线和工具结果、分支信息及必要的发送者信息。
- 请求与响应携带会话/代次标识;切会话、清屏或切分支后到达的旧响应必须丢弃。
- `messageBuffer` 只负责活动流,不得作为历史分页数据源。
- `manual` 模式向上只能点击“加载更早消息”;滚到顶部不得自动请求。`auto` 模式由顶部观察器自动请求,同时保留同一按钮。两个模式向下都自动恢复较新消息。
- 每个新流式 token 延续当前行为:立即回到底部并跟随活动消息。
- 向上换页使用“首个可见消息 ID + 像素偏移”恢复锚点,误差不超过 2 px。不得只按总高度差估算。
- 切换分支后重建链,并尽量让目标消息保持在同一视口位置;目标已不存在时回到底部。
- `clearChat()` 清除当前窗口的游标、缓存、DOM、未决请求和代次;已注入的配置模式和大小保持不变。
- 一次换页始终执行“加入一端、裁掉另一端”,并维护 `hiddenOlder``hiddenNewer`。上方没有更多记录时隐藏加载入口。
- 不引入 JSDOM、npm 工程或新的前端依赖。
**目标测试**
```text
node tests/test_render_window.js
python tests/diag_render_scale.py 400
python tests/smoke_offscreen.py
python tests/smoke_timeline.py
python tests/smoke_midswitch.py
python tests/test_file_attach.py
node tests/test_math_extract.js
```
`test_render_window.js` 必须直接测试 DOM 无关状态机;DOM/QWebChannel 集成由 Qt smoke 覆盖。
**完成证据**
- 参数化测试覆盖 `auto`/`manual`、10/40/200、全部非法配置类型、双向连续换页、首尾边界和过期响应。
- 固定 400 条夹具中 `.message-wrapper <= size`,并记录总节点数与页面高度;节点和高度阈值只对该固定夹具验收,见 `VERIFICATION.md`
- 附件消息、带工具时间线消息和多分支消息在被裁剪后再次加载,内容与控件完整。
- 有/无活动流两种情况下均满足严格上限;流式期间没有删除活动消息。
- 自动与手动模式分别有向上行为断言;两个模式都有无需点击的向下恢复断言。
- 锚点误差记录不超过 2 px;分支目标缺失的回底行为有断言。
- 帧耗时只形成真机人工基准报告,不作为自动化硬阈值。
## P1-02 Windows/Linux shell 与进程树终止
**状态:已完成。** 2026-07-09 无人值守轮次:新增 `core/platform_shell.py` 窄适配(Windows 字符串命令行 `cmd.exe /d /s /c "<cmd>"`、Linux `/bin/bash -lc` argv + 独立进程组;超时/中止整树终止);`SYSTEM_PROMPT.md` 单一通用正文 + `{{SHELL_PLATFORM_SECTION}}` 运行时短平台段。目标测试 20/20、30/30、35/35、41/41 全绿,smoke_mode 完整 agent 回合 ALL PASS。Linux 进程组用例(C3)在 Linux 环境运行时生效。证据见 `evidence/P1-02.md`
**目标**
明确工具 shell 契约,并保证超时、取消和异常清理能结束整棵子进程树。
**先读文件**
- `core/agent/tools.py` 中 bash 工具、`Popen`、超时和终止逻辑
- `core/llm_engine.py``load_system_prompt`
- `SYSTEM_PROMPT.md` 的 shell/path 说明
- `tests/test_bash_stream.py``tests/test_tool_params.py`
**允许修改**
- 上述文件和测试。
- 可以新增一个窄的平台进程适配模块及 `tests/test_cross_platform_shell.py`
**硬约束**
- Windows 明确通过 `cmd.exe` 执行;Linux 明确通过 `/bin/bash -lc` 执行,不依赖 `shell=True` 的平台默认值。
- Windows 使用现有等价机制终止进程树;Linux 创建独立 POSIX 进程组,超时和主动中止均向整组发送终止信号,并在宽限期后强制结束。
- 输出流、超时提示、截断上限和工具结果结构保持现有接口。
- 保留一份通用 `SYSTEM_PROMPT.md`;运行时只插入短的平台 shell/path 段。不得维护两份完整提示词。
- 提示词仍在每次请求时重读。Windows 段不得出现在 Linux 请求中,Linux 段不得出现在 Windows 请求中。
- 不增加命令审批、Agent shell sandbox 或路径限制。
**目标测试**
```text
python tests/test_cross_platform_shell.py
python tests/test_bash_stream.py
python tests/test_tool_params.py
python tests/test_agent_core.py
```
**完成证据**
- 平台参数测试捕获到 Windows 的 `cmd.exe` argv 和 Linux 的 `/bin/bash -lc` argv。
- Linux 用例启动父进程和孙进程,分别在超时与主动中止后证明两者都不存在;Windows 有等价进程树用例。
- 提示词测试模拟两个平台,证明只有对应平台段被插入且通用正文完全相同。
- 现有 bash 流式输出、返回码、超时和截断回归全部通过。
## P1-03 Windows/Linux 渲染器启动链
**状态:已完成。** 2026-07-17 无人值守轮次:新增 `core/renderer_backend.py` 窄适配(后端解析:非法/跨平台 `webview2` 可见警告 + 回落平台默认;每实例独立 QtWebEngine profile 目录:源码 `data/webengine/profile_<pid>_<ms>`、测试经 `HAOCODE_WEBENGINE_PROFILE_DIR` 重定向;`--no-sandbox` 仅显式 + root/容器时接受并打印高可见警告,普通桌面剥离);`main_window.py``sys.platform == "win32"` 门控 `core.webview2` 导入(Linux 永不触达 pythonnet/Win32/DLL/taskkill);`CustomWebPage(profile, parent)` 兼容旧式调用;`main.py` 导入 PyQt6 前 sanitize flagsrequirements 平台 marker + Linux 运行说明。目标测试 19/19、10/10、22/0、8/8、39 passed 全绿 + `main.py` offscreen 真实启动链验证;Linux 真机与 root/容器接受路径待对应环境。证据见 `evidence/P1-03.md`
**目标**
建立明确的渲染器矩阵:Windows 首选 WebView2、失败回落 QtWebEngineLinux 只使用 QtWebEngine。
**先读文件**
- `main.py`
- `ui/views/main_window.py` 中浏览器创建、JS 就绪和配置读取
- `core/webview2.py`
- `ui/views/wv2_view.py`
- `ui/views/custom_web_page.py`
- `requirements.txt`
- `tests/test_wv2_guard.py``tests/smoke_offscreen.py`
**允许修改**
- 上述启动链、依赖说明和测试。
- 可以新增小型平台检测/QtWebEngine profile 适配器和 Linux 源码运行说明。
**硬约束**
- Linux 路径不得导入或调用 pythonnet、Win32 API、WebView2 DLL 和 `taskkill`
- 不引入 WebKitGTK 或它的 Qt 封装。
- QtWebEngine 使用隔离 profile,两个并行源码实例不得争用同一个 Chromium profile。保持现有页面功能,不伪造 Linux 单实例锁。
- 非法 `webview_backend` 配置产生可见警告并回落平台默认值,不能阻断源码启动。
- 正常桌面运行保留 Chromium sandboxroot/container 的无 sandbox 路径必须显式选择并打印风险提示。
- `vendor/webview2/` 和根目录 `WebView2Loader.dll` 保持 Windows 运行时用途,不删除。
- 不添加 PyInstaller/spec 文件或打包承诺。
**目标测试**
```text
python tests/test_wv2_guard.py
python tests/smoke_offscreen.py
python tests/test_debug_window.py
node tests/test_math_extract.js
```
平台真实启动按 `VERIFICATION.md` 执行。
**完成证据**
- 平台模拟测试证明 Windows 的首选/回落路径和 Linux 的 QtWebEngine-only 路径。
- Linux import 测试不触达任何 Windows-only 符号。
- 两个 QtWebEngine 实例同时运行、载入本地页面和关闭,profile 无锁冲突或互相清理。
- Windows 真实 WebView2 和强制 QtWebEngine 回落各有一次启动记录;Linux X11/Wayland 各有一次 QtWebEngine 启动记录。
- 普通用户运行参数中不存在 `--no-sandbox`;显式 root/container 路径有单独证据。
## P1-04 Linux 截图热键与截图实现
**状态:已完成。** 2026-07-17 无人值守轮次:新增三个窄适配器——`desktop_session.py`(会话探测 win32/x11/wayland/unknown + 能力路由/明确不可用日志)、`x11_hotkey.py`ctypes→libX11 XGrabKey 原生全局热键,零 pip 依赖,注册失败/无显示明确日志,stop 释放)、`portal_capture.py`Wayland 经 xdg-desktop-portal Screenshot,系统 gdbus CLI,用户授权不绕过 compositorFilePicked→现有附件流程,拒绝/不支持/超时有明确结果);`main_window.py` 热键与截图按平台路由(Windows 行为保持);`screen_capture.py` 空画面守卫。目标测试 23/23、17/17、9 OK、8/8 全绿 + 回归 41/41 等全绿 + `main.py` offscreen 真实启动链;X11 真实注册/命中与 Wayland portal 三态待真实 Linux 宿主手动。证据见 `evidence/P1-04.md`
**目标**
把现有“截图全局热键”能力适配到 Linux,而不是扩展成通用键盘钩子系统。
**先读文件**
- `ui/views/system_tools/global_hotkey.py`
- `ui/views/system_tools/screen_capture.py`
- `ui/views/main_window.py` 中热键注册、截图启动和结果处理
- `main.py` 的平台启动逻辑
- 相关附件/图片测试
**允许修改**
- 上述文件和测试。
- 可以新增 Windows、Linux X11、Linux Wayland 的窄适配器;原入口保持稳定。
**硬约束**
- Windows 保持现有行为。
- Linux X11 使用原生全局快捷键和可行的原生屏幕捕获路径。
- Linux Wayland 使用 `xdg-desktop-portal` 或桌面协议完成快捷键和截图;遵守用户授权流程,不绕过 compositor。
- portal、桌面环境或协议版本不支持时,界面/日志必须明确说明当前能力不可用,主程序仍可聊天和使用其他功能。
- 只处理现有截图快捷键,不增加任意键监听、记录或重映射。
- 不做打包和发行版泛化;Ubuntu 22.04/24.04 x64 以外标记“未验证”。
**目标测试**
```text
python tests/test_global_hotkey_platforms.py
python tests/test_screen_capture_platforms.py
python tests/test_file_attach.py
python tests/smoke_offscreen.py
```
适配器单元测试使用替身;X11/Wayland 真实行为按 `VERIFICATION.md` 手工取证。
**完成证据**
- 平台路由、授权拒绝、portal 缺失和注册失败均有确定的回归测试。
- X11 真实桌面中,应用失焦时热键仍触发截图,图片回到现有附件流程。
- Wayland 真实桌面中,通过 portal/桌面协议完成授权、触发和截图;若目标桌面确实不支持,留下明确错误和环境信息,而不是伪造成功。
- Windows 现有热键和截图回归通过。
## P2-01 Bash 任务按启动时间倒序
**目标**
运行中和已完成两栏都让最新启动的任务位于第一项,任务从运行中移动到已完成时仍使用原启动顺序。
**先读文件**
- `ui/views/bash_panel.py``set_session``_refresh``on_started``on_finished``BashLayer` 和两个 section 类
- `ui/views/main_window.py``_on_tool_started`、输出/计时/完成转发
- `tests/smoke_bash_panel.py`
**允许修改**
- `ui/views/bash_panel.py`、必要的事件元数据传递和测试。
**硬约束**
- 排序键是启动时间/启动序号,降序显示;不得用完成时间重排。
- 已落库时间线没有显式时间时,使用消息链顺序和时间线内顺序构造稳定启动序号,不改数据库 schema。
- 任务从运行中进入已完成后,相对顺序由原启动键决定,不因结束先后跳位。
- 重排复用现有 `BashLayer` 实例;展开/折叠状态、实时输出、代码框水平/垂直滚动值、运行中栏和已完成栏的 section 滚动位置都保持。
- 保留现有限量显示和上下文标记语义。
**目标测试**
```text
python tests/smoke_bash_panel.py
python tests/test_bash_stream.py
```
**完成证据**
- 至少三项任务以不同启动/完成顺序运行,两个 section 均断言启动时间降序。
- 完成中间任务前后,对象 identity、展开状态、输出文本和四类滚动值保持。
- 切换会话后从 DB/活动流重建的顺序与实时期间一致。
**状态:已完成。** 2026-07-21 无人值守轮次:`bash_panel.py``_refresh()` 两栏显示改为启动序号降序(排序键 = `_order` 稳定启动序号,构造方式 = 消息链顺序 + 时间线内顺序,无时间戳、无 schema 变更;完成时间从不参与排序,`on_finished` 不移动 `_order` 位置);重排仍走 `set_layers` 复用同一批 `BashLayer` 实例,展开/折叠、实时输出、代码框滚动、栏滚动位置全部保持;限量窗口成员与提示语不变。`smoke_bash_panel.py` 新增第 11 节 23 项断言(交错完成顺序、对象 identity、四类滚动值保持、DB 重建一致性)140 项 ALL PASS`test_bash_stream.py` 30/30,回归 smoke_offscreen 8/8、run_tests 41/41 全绿。证据见 `evidence/P2-01.md`
## P2-02 右侧 Bash 面板滚动条
**目标**
只修复右侧 Bash 面板的原生滚动条和横纵滚动条交汇角,不污染其他 Qt 控件。
**先读文件**
- `ui/views/main_window.py` 中全局 QSS 的 `#right_sidebar``#bl_code`
- `ui/views/bash_panel.py``BashLayer._code_box` 和 section 滚动区
- Qt 当前版本的 `QAbstractScrollArea::corner`/`QPlainTextEdit::corner` 样式行为
- `tests/smoke_bash_panel.py`
**允许修改**
- 右侧面板的局部 QSS、相关测试和新建 `tests/diag_panel_scrollbar.py`
**硬约束**
- 保留现有 `#bl_code` 背景、边框、圆角和文本基础样式,只补滚动条与正确的 corner 规则。
- 所有选择器必须限定在右侧 Bash 面板或 `#bl_code`;不得添加无作用域的 `QScrollBar`/`QAbstractScrollArea` 全局规则。
- 横纵滚动条目标厚度为 8 px,隐藏箭头,handle 可见且 hover 正常;corner 与代码框背景一致。
- 使用 Qt 支持的 `QAbstractScrollArea`/`QPlainTextEdit` corner 子控件语法,不写 `QScrollBar::corner`
- 不借机调整其他弹窗、会话列表或全局字体。
**目标测试**
```text
python tests/smoke_bash_panel.py
python tests/diag_panel_scrollbar.py
```
**完成证据**
- 离屏测试断言横纵滚动条 sizeHint/实际厚度和箭头 extent,诊断脚本生成局部截图并打印测量值。
- Windows 与 Linux QtWebEngine 真机截图均显示无箭头、无亮色 corner 方块、handle 可辨识。
- 模型弹窗、会话列表、附件预览和调试窗口的滚动条与修复前一致。
**状态:已完成。** 2026-07-21 无人值守轮次:`main_window.py` 全局 QSS 中 `#bl_code` 规则后插入一段完全限定作用域的滚动条 + corner 规则(`QPlainTextEdit#bl_code QScrollBar:*``QPlainTextEdit#bl_code::corner { background-color: #fbfcfe; }``QScrollArea#bl_scroll QScrollBar:*`)——零无作用域规则;`#bl_code` 原有背景/边框/圆角/文本样式未动。新建 `tests/diag_panel_scrollbar.py`18 项断言:厚度/sizeHint/箭头 subControlRect=0/corner 像素 #fbfcfe/无亮白/未命名框仍原生 14px/附件区仍 6px18 项 ALL PASS`smoke_bash_panel.py` 140 项、回归 smoke_offscreen 8/8、run_tests 41/41、timeline 11/11、midswitch 7/7 全绿。截图落盘 `evidence/p2-02-*.png`。Windows/Linux 真机截图项:在对应环境直接运行 `diag_panel_scrollbar.py` 即可出图验证。证据见 `evidence/P2-02.md`
## P2-03 WebView2 原生窗口遮挡层
**目标**
修复改名遮罩被 WebView2 原生子窗口压住的确定性缺陷,并审计同类遮罩,只修复能确认的原生窗口遮挡。
**先读文件**
- `ui/views/main_window.py``AttachmentPreviewOverlay``RenameOverlay``SessionContextPopup``SettingsWindow`、所有以 `bg_widget` 或主窗口为 parent 的全窗口候选,以及 `_rename_session`
- `ui/views/wv2_view.py`
- `core/webview2.py` 中原生子窗口层级说明
- `tests/smoke_offscreen.py`
**允许修改**
- 上述遮罩实现和测试。
- 可以新增 `tests/diag_rename_overlay.py`;只有出现真实重复时才可加一个窄的几何同步辅助函数。
**硬约束**
- `RenameOverlay` 使用可覆盖原生 WebView2 的独立顶层透明窗口,覆盖主窗口客户区并保持卡片居中;系统标题栏和窗口控制仍可操作。
- 主窗口移动、缩放、最大化、还原和多显示器/DPI 变化时几何同步正确。
- 保留点击空白关闭、Esc、关闭按钮、输入框全选、Enter 提交和关闭后焦点恢复。
- 顶层窗口关闭后释放,不残留遮罩、事件过滤器或焦点捕获。
- 审计每个遮罩后只修复可复现的同类问题;普通 popup/dialog 不重写成统一框架。
- QtWebEngine 路径行为不得退化;真正的 WebView2 覆盖只能在 Windows 真机验收。
**目标测试**
```text
python tests/diag_rename_overlay.py
python tests/smoke_offscreen.py
python tests/smoke_copy_session.py
```
**完成证据**
- 结构测试证明遮罩是顶层 Tool 窗口、启用透明背景、覆盖主窗口客户区并可释放。
- Windows WebView2 真机截图显示聊天区与 Qt 区域均匀变暗,卡片在最上层且可交互。
- 拖动、缩放、最大化、还原、多 DPI 显示器至少各验证一次;记录覆盖误差。
- 遮罩审计表列出每个候选、是否受原生窗口影响、复现结果和处理决定。
**状态:已完成。** 2026-07-21 无人值守轮次:`RenameOverlay` 整类重写为独立顶层 `FramelessWindowHint|Tool` 透明窗(`WA_TranslucentBackground` + `WA_DeleteOnClose`)——只覆盖主窗口**客户区**`mapToGlobal(main.rect().topLeft())`+客户区尺寸,系统标题栏/窗口控制保持可操作),eventFilter 跟随 `Move`/`Resize`/`WindowStateChange`(最大化/还原/DPI 变化同路),保留点空白/✕/取消/Enter/输入全选,新增 Esc 关闭;关闭时 `removeEventFilter`+焦点回主窗+`deleteLater``_closed` 幂等。调用点 `_rename_session` 未改。审计:`AttachmentPreviewOverlay`/`SettingsWindow`/`SessionContextPopup`/`ModelSelectPopup`/`SessionModePopup`/`PdfModePopup` 均为独立顶层窗(不受影响),`RenameOverlay` 是唯一 bg_widget 子控件模式者(已修)。未抽公共辅助函数(不构成真实重复);未重写普通 popup/dialog。新建 `tests/diag_rename_overlay.py`(34 项:结构/客户区覆盖/跟随/全部关闭与提交行为/释放无残留)ALL PASS;目标测试 smoke_offscreen 8/8、smoke_copy_session ALL PASS;回归 smoke_bash_panel 140 项、run_tests 41/41 全绿(均 EXIT=0)。真机 WebView2 验收:`core/webview2.py` `get_environment()``taskkill /F /IM msedgewebview2.exe` 且共享 profile 不可重定向,无人值守自动化有意不走该路径;机制与生产已验证的附件预览层同构(拥有者顶层 Tool 窗 z 序必盖 WebView2 原生子 HWND),人工验收清单见 `evidence/P2-03.md`。证据见 `evidence/P2-03.md`
## P2-04 跨平台聚合测试入口
**目标**
提供一个可在 Windows/Linux 调用的自动化聚合入口,同时保留每个现有测试的独立运行方式。
**先读文件**
- `tests/run_tests.py`
- `tests/test_*.py``tests/smoke_*.py` 的入口和环境假设
- 本文各任务新增的测试
- `VERIFICATION.md`
**允许修改**
- `tests/run_tests.py`,或新增 `tests/run_all.py`
- 可以新增测试清单/分组元数据和必要的测试环境辅助模块。
- 可以更新交接测试文档。
**硬约束**
- 保留所有独立命令;聚合入口不得要求 pytest、npm 或网络。
- 每个测试使用独立临时目录;GUI 测试在 import `MainWindow` 前完成临时数据库与临时配置设置。
- 默认聚合不运行 `diag_*``verify_*``tune_*`、真实 API、真实桌面或需要凭据的脚本。
- 平台不适用项必须以明确 `SKIP` 和理由呈现,不能伪装通过;平台共同项失败时返回非零。
- 一个子测试崩溃或超时不能阻止结果汇总,最终退出码仍反映失败。
- 不读取真实配置,不访问网络,不删除用户运行数据。
**目标测试**
```text
python tests/run_all.py --group logic
python tests/run_all.py --group offscreen
python tests/run_all.py --group all
```
若选择扩展现有 `run_tests.py`,保持等价分组参数,并同步本文命令。
**完成证据**
- Windows 和 Linux 各有一份汇总,列出 PASS/FAIL/SKIP、耗时和失败命令。✅ Windows 25/25 PASSlogic 12 + offscreen 13,均 EXIT=0);WSL/CPython 3.12.3 全量 PASS 4 / FAIL 0 / SKIP 21(依赖缺失/平台不适用,理由逐条打印,EXIT=0)。
- 用一个故意失败的临时夹具证明聚合入口返回非零且仍汇总后续测试;夹具不提交。✅ 崩溃夹具→FAIL、超时夹具→TIMEOUT,后续健康测试照跑,退出码 1,完整汇总 + 失败命令清单;夹具用后即删。
- 默认运行记录证明未启动真实 API 测试、人工诊断脚本或真实桌面脚本。✅ 默认清单仅 25 条自动化条目;diag_*/verify_*/tune_*/real-DB/凭据类均在排除清单带理由(见 run_all.py 注释)。
- 各单文件命令仍可直接运行,输出与聚合子进程一致。✅ `test_copy_session.py` 独立 vs 聚合子日志尾逐字一致(54/54 PASS)。
## 整体验收终点
只有同时满足以下条件,本清单才完成:
1. 每个任务的目标测试通过,并留下该任务要求的证据。
2. `VERIFICATION.md` 的完整自动化回归在 Windows 和 Linux 通过;合理的平台专属项明确跳过。
3. Windows WebView2、Windows QtWebEngine 回落、Linux X11 QtWebEngine、Linux Wayland QtWebEngine 均完成真实桌面检查。(Windows 两项:✅ 2026-09-17 已验证,见 evidence/P1-03.mdLinux 两项:待 Ubuntu 桌面主机)
4. X11 与 Wayland 的截图热键按各自协议验证;不支持的 Wayland 环境留下明确错误证据。
5. 没有业务功能扩展、模块搬迁、打包变更、Agent sandbox 或真实配置访问混入修复。