Files
Haocode/docs/agent-handoff/PLATFORM_PLAN.md
T
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

154 lines
12 KiB
Markdown
Raw 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.
# Windows / Linux 源码运行契约
> 触发条件:修改启动、浏览器、shell、进程取消、系统热键、截图、路径、依赖或平台测试前读取。本文定义目标行为,不代表这些能力已经实现。只有完成本文验收矩阵后,才能把对应环境标为“已验证”。
## 支持矩阵
| 环境 | Python | 浏览器后端 | 系统集成 | 状态定义 |
| --- | --- | --- | --- | --- |
| Windows x64 | 现有 Python 3.10 基线 | WebView2 首选;失败或 profile 被占用时回落 QtWebEngine | 现有 Windows 原生全局热键和截图 | 必须保持现有产品行为 |
| Ubuntu 22.04/24.04 x64 + X11 | Python 3.103.12 | 仅 QtWebEngine | X11 原生全局热键;现有截图交互的 X11 实现 | 本阶段 Linux 主支持目标 |
| Ubuntu 22.04/24.04 x64 + Wayland | Python 3.103.12 | 仅 QtWebEngine | `xdg-desktop-portal` 全局快捷键/截图能力 | portal 缺失或桌面不支持时明确报错 |
| `QT_QPA_PLATFORM=offscreen` | 同对应系统 | QtWebEngine 测试路径;Windows 跳过 WebView2 | 不验证真实全局热键和桌面截图 | 只用于自动化测试,不算桌面支持证据 |
其他 Linux 发行版可以尝试源码运行,但必须标记“未验证”。Linux 不引入 WebKitGTK、WebKitGTK 的 Qt 包装层或第二套网页 UI。
## 平台选择原则
平台能力通过小型适配器隔离,不建立通用插件框架,也不移动现有模块。适配器至少覆盖以下三类能力:
- 浏览器选择:Windows 尝试 WebView2 后回落 QtWebEngine;非 Windows 直接进入 QtWebEngine。
- shell 生命周期:生成固定 shell argv、设置进程组选项、取消和超时时终止完整命令树。
- 桌面集成:注册/注销 `Alt+S`,启动截图并以现有 Qt 信号返回结果或错误。
Windows 专用模块和 Linux 专用模块只在平台选择完成后延迟导入。Linux 启动链不得先访问 `ctypes.WinDLL`、Windows HWND、`taskkill` 或 WebView2 DLLWindows 启动链不得依赖 DBus、portal 或 X11 包。
## 浏览器后端
### Windows
1. 保持 WebView2 为首选,初始化失败时显示可诊断日志并回落 QtWebEngine,主窗口仍可使用。
2. 保持现有实例锁语义。同一 WebView2 profile 已由另一个实例使用时,新实例回落 QtWebEngine,不与其争用 profile,也不结束对方进程。
3. WebView2 原生子窗口继续使用现有包装层;修复弹层时只调整已确认的遮挡案例。
4. 强制 QtWebEngine 回落必须有测试入口,以便在 Windows 上验证两条渲染路径。
### Linux 与 QtWebEngine 回落
1. 非 Windows 平台不探测、不导入、不加载 WebView2。
2. 每个 QtWebEngine 应用实例使用独立 profile/storage 目录,避免多个 Chromium 实例争用同一 profile。源码阶段目录仍位于项目 `data/` 范围;自动化测试使用临时目录。
3. 保持离线加载 `ui/web/` 资源;路径拼接必须兼容大小写敏感文件系统。
4. QtWebEngine 必须在 `QApplication` 创建前完成项目所需的导入和 Chromium 环境设置。
5. 保留现有软件渲染/GPU 配置入口。无效可选配置应输出明确警告并回落可启动默认值,不能让源码运行因可选渲染配置直接失败。
**Linux 源码运行(最小步骤,Ubuntu 22.04/24.04 x64):**
```bash
# 1) 依赖(pythonnet/clr_loader 带 sys_platform == "win32" markerLinux 自动跳过)
python3.10 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
sudo apt install -y libnss3 libxkbcommon0 libfontconfig1 libdbus-1-3 libgl1 libegl1 libasound2t64
# 22.04 无 t64 后缀,用 libasound2
# 2) 运行(普通用户;只走 QtWebEngine;每实例独立 profiledata/webengine/profile_<pid>_*
python3.10 main.py
# 3) 仅当 root/容器启动且页面空白时,才显式禁用沙箱(启动会打印高可见风险警告):
QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox" python3.10 main.py
# 4) 离屏自动化测试:
QT_QPA_PLATFORM=offscreen HAOCODE_RENDER=software QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu \
python3.10 tests/smoke_offscreen.py
```
实现锚点:`core/renderer_backend.py`(后端解析 / profile 目录 / sandbox 标志处理);`main.py` 在导入 PyQt6 前调用 `sanitize_chromium_flags``ui/views/main_window.py``sys.platform == "win32"` 门控 `core.webview2` 导入(非 Windows 永不导入,不触达 pythonnet/Win32/WebView2 DLL/taskkill);`CustomWebPage(profile, parent)` 接收本实例独立 `QWebEngineProfile`
### Chromium sandbox
Chromium sandbox 与 agent 命令权限是两件事。默认保留 Chromium sandbox,项目代码不得普遍追加 `--no-sandbox`。仅当运行者明确为 root/容器场景配置现有 `QTWEBENGINE_CHROMIUM_FLAGS`,且启动代码确认处于该场景时才接受该标志,同时输出高可见警告。普通 Windows/Linux 桌面运行不得关闭 sandbox。
## Shell 和进程生命周期
“bash 工具”保留产品名称,但执行契约按平台固定:
| 平台 | argv | `Popen` 要求 | 取消/超时 |
| --- | --- | --- | --- |
| Windows | `cmd.exe /d /s /c <command>` | `shell=False`;保持现有流式 stdout/stderr | 终止该命令的完整 Windows 进程树 |
| Linux | `/bin/bash -lc <command>` | `shell=False``start_new_session=True`;保持流式 stdout/stderr | 向进程组发 `SIGTERM`,短暂宽限后仍存活则发 `SIGKILL` |
实现必须满足:
- 取消、超时、窗口退出三条路径复用同一个幂等终止函数。
- Linux 以 `os.getpgid(proc.pid)` 定位本次命令进程组,不按进程名杀进程,也不遗留孙进程。
- 正常退出不进入强杀路径;进程已经结束时重复取消不抛出用户可见异常。
- `cwd`、环境变量、编码和分块输出保持现有工具语义。命令字符串不在 Python 中按 `;``&&` 或管道自行拆分。
- 平台 shell 名称进入诊断日志,避免把 Linux `/bin/sh` 误报成 bash。
## 系统提示词
只维护根目录一份 `SYSTEM_PROMPT.md`。每次请求继续重新读取公共正文,再由运行时生成一段短的平台信息并插入请求,不创建 Windows/Linux 两份完整提示词。
运行时段至少声明:
- 当前操作系统和 shellWindows `cmd.exe` 或 Linux `/bin/bash -lc`
- 路径格式和路径分隔符;Linux 文件名大小写敏感。
- 对应 shell 的环境变量、命令连接和引号规则。
- agent 没有额外 shell 沙盒或命令审批层,不要虚构这些能力。
平台段是请求构造的一部分,不写入会话历史,不参与压缩持久化。契约测试应验证公共提示词只有一份,且不同平台只改变运行时段。
## 配置和路径
1. 所有配置读取入口统一尊重 `HAOCODE_CONFIG_FILE`;测试在导入 UI/LLM 模块前把它指向临时文件。真实凭据文件按 [README.md](README.md) 的边界处理。
2. 所有自动化测试同时把默认数据库改到临时目录,不能依赖开发机已有数据库或附件。
3. 源码运行阶段继续使用项目内 `data/`。AppData、XDG Base Directory、安装器写入权限和配置迁移全部留到打包阶段。
4. 使用 `pathlib``os.path` 组合路径,不拼接平台分隔符;资源存在性检查覆盖 Linux 大小写差异。
5. WebView2 DLL、Windows 锁文件和 Windows 进程命令不成为 Linux 配置校验项。Linux portal/X11 依赖缺失也不能阻止不相关的聊天和 shell 功能启动。
## Linux 桌面集成
### X11
- 为现有 `Alt+S` 截图快捷键实现 X11 原生注册/注销,保持 `GlobalHotkeyThread` 对上层的信号语义。
- 注册失败时返回具体原因并保留窗口内截图按钮或应用内快捷键;不得后台忙等键盘状态。
- 屏幕像素获取走 X11 可用的原生抓屏路径;区域选择 UI 可以继续使用 Qt。
- 截图继续提供现有 `QImage` 返回语义,覆盖多显示器和负坐标桌面布局。
### Wayland
- 使用 `xdg-desktop-portal` 提供的桌面协议请求全局快捷键和截图,不使用 X11 API 假装支持 Wayland。
- portal 请求必须异步处理授权、拒绝、取消和桌面不支持四种结果;UI 只禁用受影响能力,主程序继续运行。
- 无法提供全局快捷键时给出明确、可操作的错误,不能静默降级成“看似注册成功”。窗口内按钮/快捷键仍按桌面允许范围工作。
- portal 返回的截图 URI/数据先完成有效性检查,再转换为现有 `QImage`/附件流程;临时文件生命周期由适配器负责。
会话类型优先综合 Qt 平台名和 `XDG_SESSION_TYPE` 判断,并记录最终选择。环境变量与实际 Qt 后端矛盾时,选择可证明可用的后端并输出警告。
> **实现状态(P1-042026-07-17,证据 `evidence/P1-04.md`**`ui/views/system_tools/desktop_session.py` 按上文判定顺序实现 `session_kind()`offscreen→unknownQT_QPA_PLATFORM/WAYLAND_DISPLAY/XDG_SESSION_TYPE 综合);X11 热键 = `x11_hotkey.py`ctypes→libX11 XGrabKey,同 `triggered` 信号语义,失败明确日志);Wayland 截图 = `portal_capture.py`xdg-desktop-portal Screenshot,系统 gdbus CLI,异步 worker,授权/拒绝/不支持/超时四态明确)。**Wayland 全局快捷键**依赖 compositor 桌面协议(ext-global-shortcut 等),本版本无免依赖实现 → 按上文“无法提供全局快捷键时给出明确、可操作的错误”处理(日志明确说明 + 保留应用内 Alt+S/按钮),不静默降级。真机验证待 Ubuntu 22.04/24.04 X11/Wayland 会话。
## 依赖范围
- `requirements.txt` 只加入源码确实导入的 Python 包,并使用平台 marker 隔离 Windows/Linux 专用依赖。
- Ubuntu 所需系统包另写安装说明,不把 apt 包名伪装成 pip 依赖。
- 不增加 WebKitGTK、打包器、安装器或与现有功能无关的桌面框架。
- 可选平台能力缺失时要局部报错;聊天、会话和 shell 的基本源码运行仍应可达。
## 验收矩阵
每行都要留下命令输出、日志或截图证据;人工桌面项不能用 offscreen 结果替代。具体测试分层见 [VERIFICATION.md](VERIFICATION.md)。
| 环境 | 必验操作 | 通过标准 | 证据 |
| --- | --- | --- | --- |
| Windows / WebView2 | 启动、流式聊天、弹层、截图、退出 | 选择 WebView2;弹层无已确认遮挡;退出不影响其他实例 | 后端日志 + 桌面截图 |
| Windows / 强制 QtWebEngine | 同一基本流程 | 不加载 WebView2;UI 和消息协议行为一致 | 后端日志 + 定向测试 |
| Windows / 第二实例 | 首实例占用 WebView2 profile 后启动第二实例 | 第二实例回落 QtWebEngine;首实例继续工作 | 双实例日志 |
| Ubuntu 22.04 X11 | 启动、聊天、`Alt+S`、区域截图、shell | 只使用 QtWebEngine;热键与截图成功;shell 为 bash | 日志 + 截图 + shell 测试 |
| Ubuntu 24.04 X11 | 同上 | 与 22.04 相同 | 日志 + 截图 + shell 测试 |
| Ubuntu 22.04/24.04 Wayland | portal 授权、拒绝、取消;截图和快捷键 | 授权路径可用;其余路径明确报错且主程序不退出 | portal 日志 + 桌面截图 |
| Linux 进程生命周期 | 命令创建子进程后取消和超时 | 父、子、孙进程全部退出;无同名进程误杀 | PID/进程组测试记录 |
| Windows 进程生命周期 | 命令创建子进程后取消和超时 | 本次命令树全部退出;其他进程不受影响 | PID 测试记录 |
| Windows/Linux 提示词 | 捕获请求构造结果 | 公共正文一致;shell/路径段与平台匹配;平台段不入历史 | 契约测试 |
| Windows/Linux 临时配置 | 使用临时配置和数据库运行 GUI smoke | 不访问项目真实配置/数据库;运行结束无生产数据变更 | 隔离断言 |
| Windows/Linux offscreen | 运行自动化 smoke | 可重复通过;明确不声称验证系统热键/截图 | 测试汇总 |
| root/容器 | 分别在未显式配置和显式配置下启动 | 默认保持 sandbox;显式 `--no-sandbox` 时打印警告 | 启动日志 |
平台任务完成条件:矩阵中该阶段承诺的每一行都有证据;自动化定向测试和阶段全量测试均通过;Linux 启动链没有 Windows 专用导入;实现没有引入 WebKitGTK、打包工作、通用钩子框架或新的 agent 权限模型。