# QAQ 架构总览 本文是 QAQ(DeepSeek Harness 启动容灾守卫)的架构入口文档。二次开发请先读本文,再按需深入各专项文档。 - [守卫生命周期](guard-lifecycle.zh.md) — 一次受监督启动的完整流程 - [状态存储与回滚引擎](state-and-rollback.zh.md) — state.json / 快照 / 防循环 - [UI 检测与 CDP](ui-detection.zh.md) — headless Chrome + 真实 DOM 判据 - [懒人脚本控制台与环境发现](console-and-env.zh.md) — 交互菜单 / 环境自检 / 插件挂载 - [日志系统](logging.zh.md) — 结构化多文件轮转日志 - [测试与真实集成](testing.zh.md) — 单测矩阵 / smoke / 真实 DSH 联调 --- ## 1. 项目定位 QAQ 是一个 **外部监督进程**:它以独立进程接管 `dsh web` 的启动,检测两种 DSH 无法自愈的故障形态: | 故障形态 | 表现 | 常规监控为何抓不到 | |----------|------|--------------------| | **宿主崩溃** | `dsh web` 进程退出 / 端口未开 / 启动期异常 | 需要专门盯进程存活 | | **UI 红屏** | 宿主进程正常、端口可达,但浏览器渲染 `Failed to load plugins` | `curl` 只能拿到空 `
`,服务端 HTML 由 React 运行时填充;CSS 类名是跨构建不稳定的 hash | QAQ 的检测线(L3)是唯一可靠的**非侵入**探测:用 headless Chrome 经 CDP 打开页面,读取真实 DOM 文本。 **非侵入承诺**:QAQ 不改 DSH 源码;备份插件只读配置、不改行为。 --- ## 2. 模块地图 | 模块 | 文件 | 职责 | 关键导出 | |------|------|------|----------| | 命令行 | `src/cli.ts` | 命令面解析、`dsh web` 监督入口 | `main()` | | 守卫编排 | `src/guard.ts` | 一次启动的编排:spawn → 探测 → 计数 → 回滚 | `superviseBoot()`, `GuardOptions`, `BootVerdict` | | 子进程监督 | `src/spawn-dsh.ts` | spawn `dsh web`、就绪/退出跟踪、输出捕获 | `spawnDsh()`, `DshSupervisor` | | CDP 客户端 | `src/cdp.ts` | 无依赖的 headless Chrome 驱动(WebSocket) | `launchSession()`, `findBrowser()` | | UI 检测 | `src/detector-ui.ts` | L3 文本判据、DOM 轮询 | `detectUi()`, `classifyDom()`, `FAILED_MARKER` | | 状态存储 | `src/store.ts` | state.json 原子读写、快照管理、守卫锁 | `readState()`, `writeState()`, `acquireLock()` | | 回滚引擎 | `src/rollback.ts` | 阈值判定、坏配置备份、防循环、成功记账 | `maybeRollback()`, `recordSuccess()` | | 环境发现 | `src/env.ts` | dsh/browser/port 自动发现 + 启动前自检 | `preflight()`, `resolveCommand()` | | 命令面 | `src/cli.ts` | 子命令解析 + 各命令处理器(status/backup/restore/reset/watch/dsh…) | `parseCli()` | | 真实 DSH 上下文 | `src/dsh-context.ts` | 解析真实 DSH 安装(home/profile/checkout)+ 进程/插件连接状态 | `resolveDshContext()`, `findDshPackages()` | | 交互控制台 | `src/console.ts` | 懒人脚本 CMD 菜单 GUI | `openConsole()` | | 插件挂载 | `src/install-plugin.ts` | 把 dsh-qaq 插件装入 DSH profile(bundle 机制) | `installPlugin()` | | 全屏仪表盘 | `src/tui.ts` | raw-mode TTY 仪表盘:实时状态、快捷键、语言切换 | `runTui()` | | 安装 | `src/setup.ts` | 一条命令装依赖 + 构建 | `runSetup()` | | 路径助手 | `src/paths.ts` | `$DSH_HOME` / `.qaq` / profile 路径推导 | `resolveDshHome()`, `qaqDir()`, `profileDir()` | | 日志 | `src/log.ts` | 结构化多文件轮转日志 | `Logger` | | 插件↔CLI 共享通道 | `src/shared-io.ts` | 插件与守卫之间的 JSON 心跳 / 健康状态 / `events.jsonl` | `readPluginHeartbeat()`, `pushEvent()` | | 外部守卫接管 | `src/watch.ts` | `qaq watch`:监视并非 CLI 启动的 DSH(按心跳发现),计数 + 回滚 | `watchOnce()`, `resolveWatchTarget()` | | Webhook 投递 | `src/webhook.ts` | 无依赖的启动失败 / 回滚事件 POST 通知 | `deliverWebhooks()` | | DSH 备份插件 | `packages/dsh-qaq/` | 在 DSH host 内部:真实用户对话后写 last-good 快照 + 持续向共享通道写心跳/清单/状态 | `apply()`, `isUserConversation()` | --- ## 3. 一次受监督启动的时序 ``` 用户(qaq console → [1],或 qaq dsh web) │ ▼ preflight() ── 环境自检(dsh 命令 / 浏览器 / 端口) │ fatal 错误 → 拒绝启动并给中文提示 ▼ acquireLock(home) ── PID 感知守卫锁(防双实例;陈旧锁自动回收) ▼ superviseBoot(GuardOptions) ── 守卫编排(最多 retries+1 次尝试) │ ┌──────────────────────────────────────────┐ │ │ bootAttempt(): │ │ │ spawnDsh → ready(端口就绪)→ detectUi │ │ └──────────────────────────────────────────┘ │ ├─ ok → confirmStable(稳定确认窗口 + 二次探测)→ recordSuccess → 交还子进程给调用方 │ └─ 失败 → 分类 host / ui / unknown │ ├─ unknown:不计分,报告 │ └─ host / ui:incrementFailure → 阈值判定 → maybeRollback ▼ verdict(BootHealthy | BootFailure) ├─ 健康:cli 保持子进程运行(可见窗口即 GUI),等待退出 ├─ 回滚成功:再启动一次(防循环围栏内),健康则继续监督 └─ 失败:给出 rolled-back 指引 ``` --- ## 4. 状态机(每个 profile) ``` ┌──────────────┐ │ 健康(监督中)│◄────────────┐ └──────┬───────┘ │ │ 确认窗口后 recordSuccess │ recordSuccess (清零计数 / 清围栏 / 写快照) ▼ │ ┌──────────────┐ │ │ 失败(计数 +1)│ │ └──────┬───────┘ │ │ │ 计数 < 阈值(默认3) │ 或 无 last-good 快照 │ ▼ │ ┌──────────────┐ 触发回滚 ┌──────────────┐ │ 报告失败,退出 │───────────►│ 回滚 + 重启一次 │ └──────────────┘ (或用户拒绝) └──────┬───────┘ │ 重启仍失败且 5 分钟内 ▼(防循环围栏生效) ┌──────────────┐ │ 停止,指引手动修复 │ └──────────────┘ ``` --- ## 5. 数据流(配置 → 快照 → 回滚) ``` $DSH_HOME/profiles/web/ package.json ── 声明 dsh.profile.bundles(插件层列表) cordis.patch.yml ── 用户 patch 层(QAQ 从不修改) │ 健康确认后(守卫) / 真实用户对话后(dsh-qaq 插件) ▼ $DSH_HOME/.qaq/ state.json ── 计数 / lastSuccess / lastGoodSnapshot / rolledBackAt latest-good/ ── 最近一次确认健康的配置副本(package.json + cordis.patch.yml + manifest.json) history/auto// ── 自动备份(守卫确认健康 / 插件真实对话后;独立 10 份配额) history/manual//── 手动备份(qaq backup / TUI `[3]`;独立 3 份配额) rolled-back// ── 回滚前保存的坏配置(人工恢复用) log/ ── qaq.log / error.log / access.log / host.log .guard.lock ── PID 感知守卫锁 │ 失败达到阈值 ▼ maybeRollback → 备份坏配置到 rolled-back → 覆盖 profile 配置 → 重启 ``` **快照原则**:只快照启动相关配置(`package.json` + `cordis.patch.yml`)。**绝不**纳入凭据、会话、storages、mcp-servers。 --- ## 6. 关键设计决策 | 决策 | 理由 | |------|------| | 文本级 UI 判据(`Failed to load plugins` / `