Back to home

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 dsh command on PATH, or an explicit QAQ_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 dsh runs? The guard defaults to dsh web (PATH resolution). 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 the dsh command — QAQ_DSH_CMD--cwd → a nearby DSH checkout (ancestors of the current directory, plus a sibling checkout sitting next to it, e.g. QAQ and deepseek-harness side 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

CommandPurpose
qaq dsh web [--port N] [--yes]supervised startup: detect host/UI failure -> count -> roll back when triggered -> restart (with anti-loop)
qaq statusprint 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 webzero the failure counters
qaq consoleopen 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)

  1. Install — double-click bin\qaq-install.cmd. It checks Node.js >= 22, installs dependencies (pnpm, with an npx fallback), and builds dist/qaq.mjs.
  2. Mount the backup plugin (recommended) — run bin\qaq-web.cmd, pick [6] (mount the dsh-qaq backup plugin). This adds dsh-qaq to the profile's bundle list and links the module into the profile's node_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 own cordis.patch.yml is intentionally left untouched — DSH auto-loads the plugin's patch from its bundle declaration.
  3. 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).
  4. Verify — pick [2] (view status) or run qaq status: hostFailures / uiFailures should be 0 and lastSuccess / lastGoodSnapshot present.

Everyday use

  • Start DSH the same way every time: bin\qaq-web.cmd[1]. Prefer not to start dsh web directly 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

SymptomWhat to do
Pre-launch self-check failed — dsh not foundPut dsh on PATH, set QAQ_DSH_CMD, or pass --cwd <dir> pointing at the DSH checkout
Port already in use — port busyStop the other process, or pick another port: --port N
UI red-screens again after a rollbackInspect 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 activeA 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 rollbackqaq restore --to <snapDir> --profile web with any directory under ~/.dsh/.qaq/history/ (or rolled-back/)
dsh-qaq not snapshottingThe 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 status prints the exact paths for your environment.

Supervised dsh web options

OptionMeaningDefault
--confirm-ms <ms>stable-healthy confirmation window before snapshotting20000
--ui-timeout <ms>max wait for the UI to settle during the L3 probe25000
--threshold <n>consecutive same-kind failures that trigger a rollback3
--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.innerText contains the pinned text Failed 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.jsonhostFailures, uiFailures, lastSuccess, lastFailure, lastGoodSnapshot, rolledBackAt
  • latest-good/ — the last confirmed-good profile config (package.json + cordis.patch.yml + manifest.json)
  • history/<ts>/ — up to 5 timestamped historical snapshots
  • rolled-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):

FileContent
qaq.logeverything (info + warn + error), the canonical record
error.logwarn/error only — grep for trouble fast
access.logcrash-audit trail: boot verdicts, snapshots, rollbacks, resets, plugin mounts, manual restore
host.lograw 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 load etc.), 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); --yes makes 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

PathPurpose
src/cli.tscommand surface + supervised loop
src/guard.tssuperviseBoot orchestration (host ready -> UI detect -> count/rollback)
src/spawn-dsh.tsspawn dsh web, inherit env, readiness/exit tracking
src/cdp.tsminimal CDP client (headless Chrome, no Playwright)
src/detector-ui.tsL3 text criteria
src/store.tsatomic ~/.dsh/.qaq read/write + snapshot management + lock
src/rollback.tsrollback + broken-config backup + anti-loop + success bookkeeping
src/env.tsauto-discovery + pre-launch self-check (dsh / browser / port)
src/console.tsinteractive menu GUI (lazy launcher, CMD window)
src/install-plugin.tsauto-mount the dsh-qaq backup plugin (rollback-safe)
src/paths.ts · src/log.tspath 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:

DocumentCovers
architecture.mdmodule map, boot sequence, state machine, data flow
guard-lifecycle.mdsupervised boot flow, failure classification, transient retry, confirmation window
state-and-rollback.mdstate.json, snapshots, anti-loop fence, guard lock
ui-detection.mdheadless-Chrome CDP client, L3 text criteria, probe timing
console-and-env.mdlazy launcher console, environment auto-discovery, plugin mounting
logging.mdstructured log format, four channels, rotation
testing.mdunit-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:

  1. Fork the repository and create a feature branch.
  2. Set up locally: pnpm install (Node 22+, pnpm 11 — see .nvmrc).
  3. Make your change with teststesting.md explains what each spec covers and how to add cases.
  4. Run the gates: pnpm typecheck && pnpm test && pnpm build (CI enforces these on Ubuntu + Windows).
  5. 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