* fix(ci): repair desktop rpm dependency and teardown checks Co-authored-by: Qwen-Coder <[email protected]> * test(desktop): use a counted space class in the EPEL checksum pin `scripts/lint.js --eslint` runs on every file the PR touches, and the new rpm dependency test spelled the two spaces in the pinned `echo` line as literals: `no-regex-spaces` rejects them, so Lint & Static failed on the incremental ESLint step. `[0-9a-f]{64} {2}` matches the same two spaces the workflow emits; the assertion still pins exactly what the release step writes. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmvzj4h0j9a * fix(desktop): keep the runtime group as a teardown target Teardown collected daemon pids through the zombie-aware liveness predicate and escalated SIGKILL only on leaders that were still alive, so a leader that had already exited — reaped, replaced, or an unreaped zombie — silently dropped its process group, which is teardown's only handle on it. The children the leader spawned (MCP servers, tool subprocesses) then survived and kept writing under the workspace about to be deleted, and the smoke reported a pass. Collect every recorded pid, escalate on every collected group after the drain, and keep the zombie-aware predicate on the "survived teardown" report so the false positive this PR exists to fix stays fixed. The new fixture drives a runtime whose leader exits while a writer in its group ignores SIGTERM. Also pins the EPEL download URL and digest literals, drops the duplicate local liveness predicate in favour of the shared helper, and covers the signal-probe error arm (ESRCH gone; EPERM/EACCES alive, the rule the core helper documents). Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmvzpjyde9j * fix(desktop): decide teardown on the runtime group, not its leader The round-2 escalation signals the recorded pid's group (`kill(-pid)`), but the verdict and the drain still asked `isProcessAlive(pid)`, which answers "gone" as soon as the leader exits — the exact topology `SMOKE_FIXTURE_EXIT_AFTER_SPAWN` reproduces. In that state `waitForExit` returned on its first poll, so the 5 s drain was skipped and the `survived teardown` branch was unreachable. `isTeardownTargetAlive` probes the group first and falls back to the leader where there is no group to signal (win32), so the drain and the leak verdict cover the children the group can still hold. Also drops the "mirrors the canonical … keep both in sync" claim from the desktop probe's comment: it diverges from core's `isPidAlive` on the pid <= 0 guard, the zombie exclusion on the EPERM/EACCES arm, and the vanished-/proc answer. The comment now states those differences and why teardown wants the dead-on-ENOENT direction. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmvzrp44z9n * fix(desktop): stop counting unreaped zombies as a live teardown group `waitForExit` asks `isTeardownTargetAlive` whether a daemon's group still exists. `kill(-pid, 0)` also answers for members that can no longer write, so a daemon that exited and that the container's PID 1 has not reaped kept its group "alive": the drain burned its five-second window, the second probe still returned the pid, and teardown reported "the packaged runtime survived teardown" for a runtime that was already gone. On Linux the group probe now counts only members that can still write: scan `/proc` for entries whose pgrp is the recorded pid, and report the group stopped when every member is a zombie. A scan that observes no member keeps the group probe's answer, so a hidden /proc cannot end a drain early, and the group handle still wins over the leader handle. Measured on a real zombie group (setsid + _exit, parent never reaps): /proc state Z, `kill(-pgid, 0)` exit 0, `isProcessAlive` false, and `isTeardownTargetAlive` true before this change, false after it. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmvzy4lf49w --------- Co-authored-by: Qwen-Coder <[email protected]>
Qwen Code desktop shell
This package is an isolated Tauri 2 shell around the existing Web Shell. It does not contain a second UI.
Window zoom follows Cmd/Ctrl + -/=/0 and Ctrl+wheel (a trackpad pinch), and the chosen factor is restored on the next launch. The shortcuts are captured in the webview and applied by the shell, which owns the factor: the webview engines expose no zoom UI, and WebView2's own zoom control is off by default.
Runtime layout
npm run build:runtime prepares runtime/qwen-code/ with:
- the current platform's Node.js runtime,
- the bundled
qwenCLI, - the built Web Shell under
lib/web-shell/.
The Tauri app starts qwen serve on an ephemeral loopback port with a per-launch bearer token, waits for /health, and then opens that same daemon-served Web Shell in the native window.
Use Settings → Daemon → Local Control to temporarily share the live daemon with a phone on the same Wi-Fi. The Web Shell displays a QR code, keeps the computer awake while sharing is enabled, and closes the LAN listener when the user turns it off.
Local development
From this directory:
npm install --workspaces=false
npm run build:runtime --workspaces=false
npm run dev --workspaces=false
The first two steps are one-time setup. After that, npm run dev is all you need.
build:runtime bundles the current platform's Node.js, the qwen CLI, and the built Web Shell into runtime/qwen-code/. Re-run it only when you change the CLI or Web Shell source.
Use QWEN_DESKTOP_WORKSPACE=/absolute/path to override the initial workspace. The app otherwise restores its saved primary workspace or creates ~/Documents/Qwen on first launch. QWEN_DEFAULT_WORKSPACE_DIR=/absolute/path relocates that first-launch default, matching the Electron shell. Add and switch project workspaces from the Web Shell after startup.
Debugging
Runtime log
The daemon log is written to ~/Library/Logs/com.alibaba.qwen-code/desktop-runtime.log on macOS. Tail it to see qwen serve output:
tail -f ~/Library/Logs/com.alibaba.qwen-code/desktop-runtime.log
The desktop state (saved workspace, window position, zoom factor) is stored in ~/Library/Application Support/com.alibaba.qwen-code/desktop-state.json.
WebView DevTools
Open the Web Shell's DevTools from the running window with Cmd+Option+I (macOS) or Ctrl+Shift+I (Windows/Linux). This lets you inspect network requests, console output, and the React component tree.
Environment variables
| Variable | Purpose |
|---|---|
QWEN_DESKTOP_WORKSPACE |
Override the initial workspace path |
QWEN_DEFAULT_WORKSPACE_DIR |
Relocate the first-launch default workspace directory |
QWEN_DESKTOP_DISABLE_UPDATES |
Set to 1 to disable the startup update check and prompt |
QWEN_DESKTOP_SKIP_BUILD |
Set to 1 to skip the CLI/Web Shell rebuild during build:runtime |
QWEN_CODE_ROOT |
Point to a local qwen-code checkout for the runtime bundle |
Rust tests
cargo test --manifest-path src-tauri/Cargo.toml
Releases
The Desktop Release workflow builds signed updater artifacts when dry_run is disabled. Published releases require the Tauri updater private key. macOS releases also require Apple signing and notarization credentials.
The first stable Tauri release may set electron_bridge=true to publish the macOS ZIPs and DMGs, Windows NSIS installer, x64 Linux AppImage, and their Electron 0.0.5 manifests. Leave the input disabled for later releases; the fixed desktop-latest release retains the bridge assets while desktop-latest.json advances independently.
Linux AppImage dependencies
The AppImage bundles WebKitGTK and its application libraries, but it deliberately relies on the host for the baseline libraries in the AppImage excludelist: glibc and the C++ runtime, the OpenGL/DRM drivers, X11/xcb, fontconfig/freetype/harfbuzz, zlib, and their low-level dependencies. Bundling those copies breaks against the host's graphics drivers or ABI. Install the distribution packages for the GTK/WebKit stack when using the AppImage.
The release smoke test extracts the AppImage, reads every ELF file's DT_NEEDED entries, and fails on any dependency that is neither inside the image nor on that host baseline list. A newly missing application library therefore blocks publication instead of surfacing as a runtime failure.
The macOS workflow accepts either the Tauri-era APPLE_* certificate and notarization secrets or the existing MAC_CSC_* and APPLE_NOTARY_* secrets. TAURI_SIGNING_PRIVATE_KEY must match the public key in src-tauri/tauri.conf.json.