WTStarMark
QAQ
QAQ: a launch resilience guard for DeepSeek Harness (DSH). Supervises dsh web, reads the real DOM via headless Chrome + CDP to catch host crashes and UI red-screens, and auto-rolls-back to the last known-good config. DSH 启动容灾守卫,检测宿主崩溃与 UI 红屏,自动回滚到最近一次成功配置。非侵入、一键懒人脚本、结构化日志。
- Stars
- 3
- Language
- TypeScript
- Created
- Aug 15, 2026
- Updated
- Aug 15, 2026
Introduction
QAQ — DeepSeek Harness Launch Resilience Guard
QAQ is a launch resilience guard for DeepSeek Harness (DSH). When a disrupted profile configuration prevents DSH from starting normally — a crashed host or a red-screened Web UI — QAQ automatically restores the configuration snapshot from the last successful boot and restarts, while preserving the broken config for manual recovery.
Author: WTStarMark
Non-invasive: QAQ never edits DSH source. The guard is a standalone executable that supervises the dsh web process and reads the browser's real DOM over CDP; the backup plugin only reads configuration and never changes behavior.
What it solves
DSH's Web surface has a failure mode where the host is alive but the UI red-screens: the host process runs, the port responds, yet the browser renders Failed to load plugins. Such failures are invisible to host-process monitoring and cannot be detected by curl (the server-side HTML ships an empty <div id="root">, rendered client-side). The only reliable non-invasive probe is to open the page in a headless browser and read the actual DOM. QAQ's UI-detection line is exactly that.
Requirements
- Node.js >= 22
- A Chrome/Chromium/Edge binary on the machine (used headlessly via CDP; no Playwright/Puppeteer dependency)
- The
dshcommand onPATH, or an explicitQAQ_DSH_CMD/--cwd
Install / Quick start
One-click (Windows): double-click bin\qaq-install.cmd (installs deps + builds), then double-click bin\qaq-web.cmd to open the interactive guard console — no commands to remember. (Chinese launcher variants: bin\qaq-web.zh.cmd / bin\qaq-install.zh.cmd.)
Or manually:
pnpm install
pnpm build # emits a single-file executable at dist/qaq.mjs
Take over dsh web from a visible CMD window:
bin\qaq-web.cmd [--port 3080] [--yes]
or directly:
qaq dsh web --port 3080 --yes
Which
dshruns? The guard defaults todsh web(PATHresolution). To run from the DSH source tree instead:QAQ_DSH_CMD="node --import tsx/esm apps/cli/src/bin.ts web" qaq dsh web --cwd /path/to/dsh-checkout
Pre-launch self-check:
qaq dsh web(and the console) auto-discover thedshcommand —QAQ_DSH_CMD→--cwd→ a nearby DSH checkout (ancestors of the current directory, plus a sibling checkout sitting next to it, e.g. QAQ anddeepseek-harnessside by side) →PATH— pick a Chrome/Chromium/Edge binary for the UI probe, and verify the target port is free. Problems are reported with actionable Chinese hints before anything is spawned.
Commands
| Command | Purpose |
|---|---|
qaq dsh web [--port N] [--yes] | supervised startup: detect host/UI failure -> count -> roll back when triggered -> restart (with anti-loop) |
qaq status | print a summary of ~/.dsh/.qaq/state.json |
qaq backup [--profile web] | snapshot the current profile as last-good |
qaq restore --to <snapDir> [--profile web] | restore a profile from a snapshot directory |
qaq reset --profile web | zero the failure counters |
qaq console | open the interactive menu (lazy launcher, same as bin\qaq-web.cmd) |
qaq install-plugin [--profile web] | auto-mount the dsh-qaq backup plugin into a profile |
Global: --yes auto-confirms rollbacks.
One-click console (qaq console / bin\qaq-web.cmd)
A menu in a visible CMD window. The console is bilingual: bin\qaq-web.cmd shows English, bin\qaq-web.zh.cmd shows Chinese; a bare qaq console defaults to Chinese (--lang en or $QAQ_LANG=en switches). The menu items correspond to:
[1] Start the guard (take over dsh web) — supervised launch (fresh preflight each time)
[2] View status — counters / last success / last snapshot
[3] Back up the current profile as last-good
[4] Roll back to last-good
[5] Reset failure counters
[6] Mount the dsh-qaq backup plugin — idempotent, rollback-safe (never breaks a boot)
[7] View logs (error / access / host)
[q] Quit
While a supervised dsh web is running, the guard lock is held until it exits (a second launch is refused and a stale port check can never misfire); Ctrl+C kills the supervised child so no process is left holding the port.
The console clears the screen before every menu render — the window always shows one screen (persistent header + last action result + menu) instead of stacking stale output. Detailed views (status / logs) pause with an Enter-to-return prompt.
Operations guide
First-time setup (Windows)
- Install — double-click
bin\qaq-install.cmd. It checks Node.js >= 22, installs dependencies (pnpm, with an npx fallback), and buildsdist/qaq.mjs. - Mount the backup plugin (recommended) — run
bin\qaq-web.cmd, pick [6] (mount the dsh-qaq backup plugin). This addsdsh-qaqto the profile's bundle list and links the module into the profile'snode_modules. From then on, the plugin snapshots the config every time a clean host boot settles (backup-only; it never changes DSH behavior). The profile's owncordis.patch.ymlis intentionally left untouched — DSH auto-loads the plugin's patch from its bundle declaration. - Launch — pick [1] (start the guard). The console re-runs the pre-launch self-check (dsh command, browser, port), then supervises
dsh web. Once the UI has been healthy for the confirmation window, the config is recorded as last-good and the guard keeps monitoring in the background (return to the menu anytime; the guard keeps running). - Verify — pick [2] (view status) or run
qaq status:hostFailures/uiFailuresshould be 0 andlastSuccess/lastGoodSnapshotpresent.
Everyday use
- Start DSH the same way every time:
bin\qaq-web.cmd→ [1]. Prefer not to startdsh webdirectly anymore — the guard owns the supervised process and is the only one that can detect a red screen. - If the UI red-screens (or the host crashes) 3 times in a row, QAQ offers a rollback to the last-good config with a diff preview. Accept it — the broken config is preserved under
~/.dsh/.qaq/rolled-back/for later inspection, and the guard restarts once automatically. - After a successful rollback + restart, the counters are zeroed and the anti-loop fence is cleared; the restored profile is the one you had before it broke.
Troubleshooting
| Symptom | What to do |
|---|---|
Pre-launch self-check failed — dsh not found | Put dsh on PATH, set QAQ_DSH_CMD, or pass --cwd <dir> pointing at the DSH checkout |
Port already in use — port busy | Stop the other process, or pick another port: --port N |
| UI red-screens again after a rollback | Inspect the logs and the preserved bad config: qaq console → [7], or read ~/.dsh/.qaq/log/ (error.log, access.log, host.log) |
Guard says anti-loop fence is active | A rollback already happened within the last 5 minutes. Fix the config manually (see rolled-back/), then qaq reset --profile web to clear the counters |
| Want to undo a rollback | qaq restore --to <snapDir> --profile web with any directory under ~/.dsh/.qaq/history/ (or rolled-back/) |
| dsh-qaq not snapshotting | The plugin only writes on a clean host settle; it does not write on a failed boot. Confirm it is listed in the profile bundles (qaq console → [2] shows the last snapshot) and that install-plugin reported success |
Data locations
- Guard state, snapshots, and logs:
~/.dsh/.qaq/(or$DSH_HOME/.qaq/) - Profile configs:
$DSH_HOME/profiles/<name>/(package.json+cordis.patch.yml) qaq statusprints the exact paths for your environment.
Supervised dsh web options
| Option | Meaning | Default |
|---|---|---|
--confirm-ms <ms> | stable-healthy confirmation window before snapshotting | 20000 |
--ui-timeout <ms> | max wait for the UI to settle during the L3 probe | 25000 |
--threshold <n> | consecutive same-kind failures that trigger a rollback | 3 |
--cwd <dir> | working directory for the supervised dsh (set to the checkout for source launch) | process cwd |
Detection criteria (L3, empirically verified)
- UI failure:
document.body.innerTextcontains the pinned textFailed to load plugins(stable across builds). The failure detail even names the missing plugin/service (e.g.web boot: 1 entry did not activate dsh-x: pending (waiting for service: s)). - Success: a composer business container (
<textarea>) is present and the failure marker is absent, stable for >=--confirm-ms. - No CSS class selectors: the red-screen structural classes are CSS-Module hashes (
_boot_<hash>) that change between builds.
State & storage (~/.dsh/.qaq/)
state.json—hostFailures,uiFailures,lastSuccess,lastFailure,lastGoodSnapshot,rolledBackAtlatest-good/— the last confirmed-good profile config (package.json+cordis.patch.yml+manifest.json)history/<ts>/— up to 5 timestamped historical snapshotsrolled-back/<ts>/— the broken config saved before a rollback (for manual recovery)log/— structured multi-file logs (see below)
Never snapshotted: credentials, sessions, storages, mcp-servers.
Logging (for developer troubleshooting)
Every record is one JSON line ({ ts, level, cat, phase?, msg, ...meta }) so the trail is machine-parseable, split across four files under log/, each rotating by size (256 KB → .1.log, keeping 5 copies):
| File | Content |
|---|---|
qaq.log | everything (info + warn + error), the canonical record |
error.log | warn/error only — grep for trouble fast |
access.log | crash-audit trail: boot verdicts, snapshots, rollbacks, resets, plugin mounts, manual restore |
host.log | raw supervised dsh stdout/stderr (mirrored to the visible window) |
Trigger & anti-loop
- 3 consecutive failures of the same kind (host or UI) trigger a rollback.
- Exception — definitive host crash: when the child process dies and its output carries a fail-loud boot marker (
plugin tree failed to loadetc.), it is a deterministic config error: QAQ rolls back on the first hit (effective threshold 1) instead of waiting for 3 manual runs. The anti-loop fence and the Y/N confirmation (unless--yes) still apply. - Confirmation is required by default (
Y/N);--yesmakes it fully automatic. - Declining the confirmation stops the guard without auto-restart: the broken config is left in place (preserved under
rolled-back/too) for manual recovery — the guard never restarts with an auto-confirmed rollback behind your back. - After a rollback a 5-minute anti-loop fence stops repeated auto-restarts if the restart still fails; the user is pointed at
rolled-back/.
Reliability features
- Transient-failure retry (
retries=1): suspected one-off flakes (host not ready, a client bundle that transiently fails to load) are retried once and not counted, so a Windows EBUSY does not corrupt the strike counter. A definitive host crash (death + fail-loud marker) is not retried — a retry only reproduces the same deterministic error — and it rolls back on the first hit. Every retried attempt kills its child first, so a failed boot never leaks a process that would hold the port or hang the guard. - Confirmation-window re-probe: after the first healthy DOM probe, the boot must stay stable for
--confirm-ms, then the real DOM is probed once more before a last-good snapshot is written — a boot that degrades right after first health is never recorded as good. - PID-aware guard lock: a stale lock left by a crashed guard is auto-reclaimed on the next run.
- Rollback diff preview: prints the config diff (current vs. last-good) before
Y/N. - Deterministic history retention: snapshots sort by their ISO-timestamp names, stable across restarts.
- Fast host-failure reporting: a child that exits before its port opens (or a spawn failure such as a missing command) is reported immediately instead of waiting out the full port timeout.
Testing
pnpm test # vitest unit tests (store / rollback / detector-ui / guard / spawn-dsh / env / install-plugin / log)
pnpm smoke # one-shot regression: unit tests + seed/broken/detect in an isolated home
pnpm smoke performs a real-DSH integration segment only when a checkout is available (set QAQ_SMOKE_DSH_HOME).
CI (.github/workflows/ci.yml) runs typecheck + build + unit tests + smoke on ubuntu-latest and windows-latest with Node 22 and a frozen lockfile.
Integration fixture: qaq-test-plugins/dsh-broken-theme (injects a service that never arrives -> deterministic red screen), used with tools/rollback-test.ps1 to exercise the full fail->count->rollback->recover loop on a real DSH instance.
Repository layout
| Path | Purpose |
|---|---|
src/cli.ts | command surface + supervised loop |
src/guard.ts | superviseBoot orchestration (host ready -> UI detect -> count/rollback) |
src/spawn-dsh.ts | spawn dsh web, inherit env, readiness/exit tracking |
src/cdp.ts | minimal CDP client (headless Chrome, no Playwright) |
src/detector-ui.ts | L3 text criteria |
src/store.ts | atomic ~/.dsh/.qaq read/write + snapshot management + lock |
src/rollback.ts | rollback + broken-config backup + anti-loop + success bookkeeping |
src/env.ts | auto-discovery + pre-launch self-check (dsh / browser / port) |
src/console.ts | interactive menu GUI (lazy launcher, CMD window) |
src/install-plugin.ts | auto-mount the dsh-qaq backup plugin (rollback-safe) |
src/paths.ts · src/log.ts | path helpers; structured multi-file rotating logger |
packages/dsh-qaq/ | DSH backup plugin (snapshots after host boot settles; backup-only) |
bin/ | qaq / qaq-web.cmd / qaq-install.cmd launchers (+ qaq-web.zh.cmd / qaq-install.zh.cmd Chinese variants) |
tools/ · test/ | integration/smoke scripts; vitest specs |
Documentation
Developer-oriented deep-dives for secondary development:
| Document | Covers |
|---|---|
| architecture.md | module map, boot sequence, state machine, data flow |
| guard-lifecycle.md | supervised boot flow, failure classification, transient retry, confirmation window |
| state-and-rollback.md | state.json, snapshots, anti-loop fence, guard lock |
| ui-detection.md | headless-Chrome CDP client, L3 text criteria, probe timing |
| console-and-env.md | lazy launcher console, environment auto-discovery, plugin mounting |
| logging.md | structured log format, four channels, rotation |
| testing.md | unit-test matrix, smoke, real-DSH integration, fault injection |
Chinese versions:
docs/*.zh.md(default-named files are English).
Contributing
Contributions are welcome — bug reports, feature requests, and pull requests all help make QAQ better.
Report a bug / request a feature: open an issue with reproduction steps (excerpts from ~/.dsh/.qaq/log/access.log and error.log go a long way) and your environment (OS, Node version).
Send a pull request:
- Fork the repository and create a feature branch.
- Set up locally:
pnpm install(Node 22+, pnpm 11 — see.nvmrc). - Make your change with tests — testing.md explains what each spec covers and how to add cases.
- Run the gates:
pnpm typecheck&&pnpm test&&pnpm build(CI enforces these on Ubuntu + Windows). - Open the PR with a short description of what changed and why.
Orientation: start with architecture.md, then the topic documents under docs/.
License
MIT