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.
12 KiB
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
- 保持 WebView2 为首选,初始化失败时显示可诊断日志并回落 QtWebEngine,主窗口仍可使用。
- 保持现有实例锁语义。同一 WebView2 profile 已由另一个实例使用时,新实例回落 QtWebEngine,不与其争用 profile,也不结束对方进程。
- WebView2 原生子窗口继续使用现有包装层;修复弹层时只调整已确认的遮挡案例。
- 强制 QtWebEngine 回落必须有测试入口,以便在 Windows 上验证两条渲染路径。
Linux 与 QtWebEngine 回落
- 非 Windows 平台不探测、不导入、不加载 WebView2。
- 每个 QtWebEngine 应用实例使用独立 profile/storage 目录,避免多个 Chromium 实例争用同一 profile。源码阶段目录仍位于项目
data/范围;自动化测试使用临时目录。 - 保持离线加载
ui/web/资源;路径拼接必须兼容大小写敏感文件系统。 - QtWebEngine 必须在
QApplication创建前完成项目所需的导入和 Chromium 环境设置。 - 保留现有软件渲染/GPU 配置入口。无效可选配置应输出明确警告并回落可启动默认值,不能让源码运行因可选渲染配置直接失败。
Linux 源码运行(最小步骤,Ubuntu 22.04/24.04 x64):
# 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 沙盒或命令审批层,不要虚构这些能力。
平台段是请求构造的一部分,不写入会话历史,不参与压缩持久化。契约测试应验证公共提示词只有一份,且不同平台只改变运行时段。
配置和路径
- 所有配置读取入口统一尊重
HAOCODE_CONFIG_FILE;测试在导入 UI/LLM 模块前把它指向临时文件。真实凭据文件按 README.md 的边界处理。 - 所有自动化测试同时把默认数据库改到临时目录,不能依赖开发机已有数据库或附件。
- 源码运行阶段继续使用项目内
data/。AppData、XDG Base Directory、安装器写入权限和配置迁移全部留到打包阶段。 - 使用
pathlib或os.path组合路径,不拼接平台分隔符;资源存在性检查覆盖 Linux 大小写差异。 - 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。
| 环境 | 必验操作 | 通过标准 | 证据 |
|---|---|---|---|
| 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 权限模型。