# QAQ — DeepSeek Harness 启动容灾守卫 [English](./README.md) 当 profile 配置损坏导致 DeepSeek Harness(下称 DSH)无法正常启动(宿主崩溃 **或** Web UI 红屏)时,QAQ 自动回溯到「上一次成功启动」的配置快照并重启,同时保留被回退的坏配置便于手动还原。 **作者**:WTStarMark **不侵入 DSH 源码**:守卫是独立可执行,只通过 `spawn` 进程 + CDP 读浏览器真实 DOM;备份插件只读配置不改行为。

QAQ 界面展示 QAQ interface

## 它解决什么 DSH Web 存在一种「宿主活、UI 红屏」的失败模式:宿主进程正常、端口可达,但浏览器渲染出 `Failed to load plugins`。这类失败纯监听宿主进程抓不到、纯 `curl` 抓空 root 也测不到(服务端 HTML 里 `
` 是空的,由 React 运行时渲染)。唯一可靠且不侵入的手段,是用 headless 浏览器打开页面、读取真实 DOM。QAQ 的 UI 侦测线正是这么做。 ## 环境要求 - Node.js >= 22 - 机器上有 Chrome/Chromium/Edge(经 CDP 无头驱动;无 Playwright/Puppeteer 依赖) - `dsh` 在 `PATH`,或用 `QAQ_DSH_CMD` / `--cwd` 指定 DSH 启动命令与工作目录 ## 安装 / 快速上手 **一条命令**:`qaq setup` 安装依赖并构建,然后 `qaq tui` 打开全屏实时守卫仪表盘。 手动安装: ```bash pnpm install pnpm build # 产出 dist/qaq.mjs 单文件可执行 ``` 从可见 CMD 窗口接管 `dsh web`: ```cmd ```bash qaq tui --port 3080 # 或不用仪表盘,直接单次受监督启动: qaq dsh web --port 3080 --yes ``` ``` 或直接: ```bash qaq dsh web --port 3080 --yes ``` > **用哪个 dsh 启动?** 守卫默认执行 `dsh web`(PATH 解析)。若要从 DSH 源码树启动: > > ```bash > QAQ_DSH_CMD="node --import tsx/esm apps/cli/src/bin.ts web" qaq dsh web --cwd /path/to/dsh-checkout > ``` > **启动前自检**:`qaq dsh web`(和控制台)会自动发现 `dsh` 命令——`QAQ_DSH_CMD` → `--cwd` → 就近的 DSH checkout(当前目录的祖先链,以及**与当前目录并排的兄弟 checkout**,如 QAQ 与 `deepseek-harness` 同目录并列)→ `PATH`——挑选 Chrome/Chromium/Edge 作为 UI 探测浏览器、确认目标端口空闲。发现问题会在拉起任何进程前给出中文可操作提示。 ## 命令面 | 命令 | 作用 | | -------------------------------------------- | ----------------------------------------------------------------------- | | `qaq dsh web [--port N] [--yes]` | 接管启动:侦测 host/UI 失败 -> 计数 -> 触发时回滚 -> 重启(带防死循环) | | `qaq status` | 显示 `~/.dsh/.qaq/state.json` 摘要 | | `qaq backup [--profile web]` | 手动快照当前 profile 进「手动备份」集(独立保留 3 份) | | `qaq restore --to [--profile web]` | 手动从某快照还原 profile(自动/手动集皆可) | | `qaq reset --profile web` | 清零失败计数 | | `qaq tui` / `qaq console` | 打开全屏实时仪表盘(非 TTY 时退回简洁菜单) | | `qaq setup` | 一条命令安装依赖 + 构建 | | `qaq install-plugin [--profile web]` | 自动把 dsh-qaq 备份插件挂载进 profile | 全局开关:`--yes` 自动确认回滚。 ## 仪表盘(`qaq tui` / `qaq console`) 在终端(`qaq tui`)下 QAQ 显示**全屏、自动刷新**的仪表盘——TUI 是**全能入口**:既能启动守卫(启动器模式),也能附着到外部启动的 DSH(侧载模式),还能浏览日志、管理插件。面板显示守卫状态、当前运行模式(启动器 / 侧载 / 空闲)、失败计数、last-good 快照、插件挂载状态、**日志查看器**和**插件管理器**。非 TTY 时退回一次性屏幕菜单(`qaq console`)。界面**双语**——在 TUI 内按 `10` 切换 en/zh(裸 `qaq console` 默认中文;`$QAQ_LANG` 或 `--lang` 可覆盖)。可用操作: ``` [1] 一键启动守卫(接管 dsh web) — 启动器模式:每次自检,回滚 + 重启 [2] 刷新状态面板 — 同时约每 1 秒自动刷新 [3] 手动备份当前配置(进「手动备份」集) [4] 备份/回滚列表 — 打开备份管理子屏:分「自动备份」与「手动备份」两群,选一项还原 [5] 重置失败计数 [6] 挂载 dsh-qaq 备份插件 — 幂等、失败即撤销 [7] 管理插件 — 安装 / 卸载 / 停用 / 启用 [8] 查看日志 — 全屏日志查看器(error/access/host/qaq) [9] 侧载 watch — 对外部启动的 DSH 运行持续侧载守卫(开关切换) [10] 热更新 — client 插件热更监控 + bundle/dist 自动重启开关 [11] 切换语言 en / zh [12] 退出 ``` 导航:`↑`/`↓`(或 `j`/`k`)移动选择,`Enter`/`Space` 执行,数字 `1..N` 直达动作,`q`/`Esc`/`Ctrl+C` 退出。 - **日志查看器**(`[8]`):`1`–`4` 切换 `error.log` / `access.log` / `host.log` / `qaq.log`,`↑`/`↓` 滚动,`q`/`Esc`/`Enter` 返回菜单。 - **插件管理器**(`[7]`):管理**真实的 DeepSeek Harness** 插件。它自动发现 DSH 安装(home + 源码 checkout,并通过心跳检测正在运行的进程),扫描 checkout 的 `packages/` 找到可安装的 `@deepseek-ai/dsh-*` bundle 包,列出当前 profile 里已安装/已启用的项;`↑`/`↓` 选中插件,然后 `e` 启用、`d` 停用、`u` 卸载、`i` 安装;`q`/`Esc` 返回菜单。**停用** = 保留模块但移出启动 bundle;**卸载** = 两者都移除。它绝不改动 QAQ 自己的仓库。 - **dsh-qaq 插件**:**TUI 打开时自动挂载**——若 profile 未安装/未启用 dsh-qaq,QAQ 自动完成挂载(写入 bundle 列表 + 建立模块链接),已安装启用的不受打扰(best-effort,失败仅告警)。菜单 `[6]` 是可随时重跑的**覆盖更新入口**:校验模块链接目标,指向过期/失效 QAQ 副本(junction 目标校验、孤儿链接、重建 lib)时自动修复——旧链接绝不会静默加载旧插件代码;真实目录/文件占位则拒绝替换(保护用户数据)。 - **备份管理**(`[4]`):备份列表子屏,明确区分**自动备份**与**手动备份**两群——自动备份(守卫确认健康 / 插件真实对话后自动产生,独立保留 **10** 份)与手动备份(`[3]` 或 `qaq backup` 产生,独立保留 **3** 份)互不干扰。`↑`/`↓` 移动选择、`Enter` 还原到该项、`q`/`Esc` 返回。 - **运行模式**:状态行显示当前集成模式 —— **启动器**(QAQ 拥有被监督的 `dsh web`)、**侧载**(检测到外部 DSH,或在持续监视它)、或**空闲**。 - **侧载守卫**(`[9]`):一个**开关**。首次按下会先解析外部 DSH 目标(`qaq tui --port` 指定的端口,否则用 dsh-qaq 插件心跳),固定该端口后每隔约 15s 探测一次真实 DOM——计数 host/UI 失败并在达到阈值时回滚(自动确认、CLI 决策),与 `qaq watch` 行为一致。再按 `[9]`(或退出仪表盘)即停止。状态行会显示被监视的 URL 与最近一次探测结果。 - **热更新**(`[10]`):插件热更新的三通道开关面板,全部**默认关闭**、可选启用: - `[1]` **client bundle 热更监控**——监视每个已启用 client 插件的 `lib/client.js`(DSH 的 client-hmr 会热换浏览器 fiber,无需重启)。QAQ 负责**验证**(CDP 全新页面探测 + dsh-qaq 插件清单)与**回滚**:热换前把旧 bundle 快照进 `~/.dsh/.qaq/hot-snapshots/`,验证失败时还原文件(再次触发热换回旧码)并复核,仍失败才升级到受监督重启。**它只读 `.qaq` 与 profile 文件,绝不触碰 state.json / last-good / 失败计数 / 防循环栅栏**。 - `[2]` **bundle 列表变化自动重启**——profile `package.json` 的 `dsh.profile.bundles` 变化(增删插件)需要重启才生效;开启后守卫检测到变化会自动执行**受监督重启**(kill → 重新 boot → 健康确认窗口,失败走既有回滚),即"伪更新"。需先按 `[1]` 进入启动器模式。 - `[3]` **web dist 变化自动重启**——DSH 前端 `apps/web/dist`(或已安装的 `dsh-web-frontend/dist`)重建后无法热换,开启后同样触发受监督重启。 - 插件管理器(`[7]`)里对 **client 类插件**的启用/停用走 `cordis.patch.yml`,DSH 的配置 HMR 会**即时生效**——QAQ 会轮询插件清单确认已生效(`已热生效 ✔`);DSH 离线时提示"重启后生效";失败时提示"旧树仍在运行"(DSH HMR 失败保留 last-good 树,与守卫"失败不破坏"哲学一致)。**bundle 类插件**的变更仍标记"重启后生效",可配合 `[2]` 自动重启。 受监督的 `dsh web` 运行期间,守卫锁会一直持有到它退出(期间拒绝二次启动,也不会被过期的端口检查误导);`q`/`Esc`/`Ctrl+C` 退出仪表盘时会先杀掉受监督子进程,避免进程残留占住端口。 控制台在每次渲染菜单前自动清屏——窗口永远只保留一屏内容(持久头部 + 上次操作结果 + 菜单),不再堆叠;状态/日志等详情视图会以 `[回车返回菜单]` 暂停,方便阅读。 ## 操作指南 ### 首次配置(Windows) 1. **安装** — 运行 `qaq setup`。它会检查 Node.js >= 22、安装依赖(pnpm,失败时回退 npx)、并构建 `dist/qaq.mjs`。 2. **挂载备份插件(推荐)** — 运行 `qaq tui`,按 `i` 挂载 dsh-qaq 备份插件。它把 `dsh-qaq` 加进 profile 的 bundle 列表,并在 profile 的 `node_modules` 里建好模块链接。此后插件会在**一次真实用户对话**发生后自动把配置快照到 `~/.dsh/.qaq`——因为只有人类真的发过消息才能证明这套配置可用(宿主 settle 但 Web UI 红屏的坏配置永远不会被记为 good,见下方"可疑 last-good")。仅备份、绝不改 DSH 行为。profile 自己的 `cordis.patch.yml` 故意不动——DSH 会从 bundle 声明自动加载插件的 patch 层。 3. **启动** — 按 `1` 一键启动守卫。控制台会重新做启动前自检(dsh 命令、浏览器、端口),然后接管 `dsh web`。UI 稳定通过确认窗口后,配置被记为 last-good,守卫转入后台持续监控(随时可回车回菜单,守卫继续运行)。 4. **验证** — `qaq status`:`hostFailures` / `uiFailures` 应为 0,且存在 `lastSuccess` / `lastGoodSnapshot`。 ### 日常使用 - 每次都用同一方式启动 DSH:`qaq tui` → 按 `1`。之后尽量不要再直接跑 `dsh web`——守卫是唯一能发现红屏的监督者。 - 若 UI 连续红屏(或宿主崩溃)**3 次**,QAQ 会给出回滚确认(带 diff 预览)。接受即可——坏配置会保留在 `~/.dsh/.qaq/rolled-back/` 供事后检查,守卫会自动重启一次。 - 回滚 + 重启成功后,失败计数清零、防死循环栅栏解除;恢复的配置就是坏掉之前的那份。 ### 故障排查 | 现象 | 处理方法 | | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `启动前自检未通过`(找不到 dsh) | 把 `dsh` 加进 `PATH`、设置 `QAQ_DSH_CMD`,或用 `--cwd ` 指向 DSH 源码目录 | | `端口已被占用` | 停掉占用进程,或用 `--port N` 换端口 | | 回滚后 UI 仍然红屏 | 看日志与保留的坏配置:`qaq tui`(仪表盘内直接看日志),或直接读 `~/.dsh/.qaq/log/`(`error.log` / `access.log` / `host.log`) | | 提示 `anti-loop fence is active` | 5 分钟内已发生过回滚。先手动修复配置(见 `rolled-back/`),再 `qaq reset --profile web` 清计数 | | 想撤销一次回滚 | `qaq restore --to --profile web`,`snapDir` 用 `~/.dsh/.qaq/history/auto/`(或 `history/manual/`、`rolled-back/`)下任意目录 | | dsh-qaq 不写快照 | 插件只在**真实用户对话发生后**写 last-good——宿主 settle 但 UI 红屏、或一直无人对话都不写。确认 profile 已含 `dsh-qaq` bundle(`qaq console` → **[2]** 能看到最近快照)且 `install-plugin` 报成功 | ### 数据位置 - 守卫状态、快照、日志:`~/.dsh/.qaq/`(或 `$DSH_HOME/.qaq/`) - profile 配置:`$DSH_HOME/profiles//`(`package.json` + `cordis.patch.yml`) - `qaq status` 会打印你环境下的确切路径。 ## `qaq dsh web` 调优参数 | 参数 | 含义 | 默认 | | ------------------- | ------------------------------------------------------ | ---------- | | `--confirm-ms ` | 稳定健康确认窗口(成功判定前的观察时长) | `20000` | | `--ui-timeout ` | L3 UI 侦测最长等待 | `25000` | | `--threshold ` | 触发回滚的连续同类失败数 | `3` | | `--cwd ` | 被监督 `dsh` 的工作目录(源码启动时指向 DSH checkout) | 本进程 cwd | ## 侦测判据(L3,实证) - **UI 失败**:`document.body.innerText` 含固定文本 `Failed to load plugins`(跨构建稳定);异常详情直接给出缺失插件/服务(如 `web boot: 1 entry did not activate dsh-x: pending (waiting for service: s)`)。 - **成功**:出现 composer 业务容器(`