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.
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
# Windows / Linux 源码运行契约
|
||||
|
||||
> 触发条件:修改启动、浏览器、shell、进程取消、系统热键、截图、路径、依赖或平台测试前读取。本文定义目标行为,不代表这些能力已经实现。只有完成本文验收矩阵后,才能把对应环境标为“已验证”。
|
||||
|
||||
## 支持矩阵
|
||||
|
||||
| 环境 | Python | 浏览器后端 | 系统集成 | 状态定义 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Windows x64 | 现有 Python 3.10 基线 | WebView2 首选;失败或 profile 被占用时回落 QtWebEngine | 现有 Windows 原生全局热键和截图 | 必须保持现有产品行为 |
|
||||
| Ubuntu 22.04/24.04 x64 + X11 | Python 3.10–3.12 | 仅 QtWebEngine | X11 原生全局热键;现有截图交互的 X11 实现 | 本阶段 Linux 主支持目标 |
|
||||
| Ubuntu 22.04/24.04 x64 + Wayland | Python 3.10–3.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 DLL;Windows 启动链不得依赖 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" marker,Linux 自动跳过)
|
||||
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;每实例独立 profile:data/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 两份完整提示词。
|
||||
|
||||
运行时段至少声明:
|
||||
|
||||
- 当前操作系统和 shell:Windows `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-04,2026-07-17,证据 `evidence/P1-04.md`)**:`ui/views/system_tools/desktop_session.py` 按上文判定顺序实现 `session_kind()`(offscreen→unknown;QT_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 权限模型。
|
||||
Reference in New Issue
Block a user