# Qwen Code Electron Desktop Implementation Plan This plan tracks the incremental MVP implementation for the Electron desktop client described in `docs/design/qwen-code-electron-desktop/qwen-code-electron-desktop-architecture.md`. The architecture document is the source of truth; this file records execution order, verification, decisions, and remaining work. ## Ground Rules - Use Electron only; do not introduce Tauri. - Keep the desktop shell thin: Electron main owns windows, native IPC, and the local server lifecycle. - Reuse Qwen Code ACP, core services, and shared web UI as later slices reach those layers. - Renderer must run with `nodeIntegration: false`, context isolation enabled, and a preload whitelist. - The local server must bind only `127.0.0.1`, use a random token, and reject unauthorized requests. - Every completed slice must leave passing targeted checks and a conventional commit. ## Task Breakdown ### Slice 1: Desktop Workspace Skeleton and Health Service - Status: complete - Goal: add the first runnable desktop package with Electron main/preload, React renderer, and a local authenticated `/health` endpoint. - Files: - `packages/desktop/package.json` - `packages/desktop/tsconfig*.json` - `packages/desktop/vite.config.ts` - `packages/desktop/src/main/**` - `packages/desktop/src/preload/**` - `packages/desktop/src/server/**` - `packages/desktop/src/renderer/**` - `scripts/build.js` - `package-lock.json` - Acceptance criteria: - `packages/desktop` is recognized as an npm workspace. - Main starts `DesktopServer` before creating the window. - Preload exposes only typed `qwenDesktop` methods. - Renderer fetches server info through preload and calls `/health` with a bearer token. - `/health` returns success only for valid token and allowed origin. - Desktop build is included after reusable packages in the root build order. - Verification: - `npm install --workspace=packages/desktop` - `npm run test --workspace=packages/desktop` - `npm run typecheck --workspace=packages/desktop` - `npm run build --workspace=packages/desktop` ### Slice 2: Desktop Server Runtime Surface - Status: complete - Goal: add `/api/runtime` and typed error responses that expose CLI path, platform, desktop version, and auth/account placeholders without spawning ACP. - Files: - `packages/desktop/src/server/http/router.ts` - `packages/desktop/src/server/services/runtimeService.ts` - `packages/desktop/src/renderer/api/client.ts` - `packages/desktop/src/renderer/App.tsx` - Acceptance criteria: - Runtime route is token protected. - Renderer shows runtime summary without exposing secrets. - Tests cover success, unauthorized, and unknown route errors. - Verification: - `npm run test --workspace=packages/desktop` - `npm run typecheck --workspace=packages/desktop` - `npm run build --workspace=packages/desktop` ### Slice 3: ACP Process Client Wrapper - Status: complete - Goal: implement a desktop-local ACP child-process client around `qwen --acp --channel=Desktop`. - Files: - `packages/desktop/src/server/acp/AcpProcessClient.ts` - `packages/desktop/src/server/acp/AcpEventRouter.ts` - `packages/desktop/src/server/services/sessionService.ts` - Acceptance criteria: - Development mode can spawn the repository CLI ACP entrypoint. - Production path is isolated behind a resolver for packaged `dist/cli.js`. - Tests mock ACP transport and cover initialize, list, new, load, and close. - Verification: - `npm run test --workspace=packages/desktop` - `npm run typecheck --workspace=packages/desktop` - targeted ACP smoke command when credentials are not required ### Slice 4: Session REST API - Status: complete - Goal: add session create/list/load/delete/rename endpoints backed by ACP. - Files: - `packages/desktop/src/server/http/router.ts` - `packages/desktop/src/server/services/sessionService.ts` - `packages/desktop/src/renderer/stores/sessionStore.ts` - Acceptance criteria: - Session routes enforce token and origin rules. - Renderer can create a session for a selected cwd and list existing sessions. - Failed ACP operations return typed retryable/non-retryable errors. - Verification: - `npm run test --workspace=packages/desktop` - `npm run typecheck --workspace=packages/desktop` - manual DesktopServer smoke with fake ACP client ### Slice 5: WebSocket Chat Loop - Status: pending - Goal: add per-session WS connections and send user prompts through ACP. - Files: - `packages/desktop/src/server/ws/SessionSocketHub.ts` - `packages/desktop/src/server/acp/AcpEventRouter.ts` - `packages/desktop/src/renderer/api/websocket.ts` - `packages/desktop/src/renderer/stores/chatStore.ts` - Acceptance criteria: - WS handshake validates session id and token. - One active prompt per session is enforced. - Renderer receives normalized assistant/tool/usage events. - Verification: - `npm run test --workspace=packages/desktop` - fake ACP integration test for prompt and stream completion ### Slice 6: Permission Bridge - Status: pending - Goal: route ACP permission and ask-user-question callbacks to renderer and resolve responses with timeout cancellation. - Files: - `packages/desktop/src/server/acp/permissionBridge.ts` - `packages/desktop/src/server/ws/SessionSocketHub.ts` - `packages/desktop/src/renderer/stores/chatStore.ts` - Acceptance criteria: - Permission requests are visible to the active session. - Closing a WS connection cancels pending requests. - Timeout defaults to deny/cancel. - Verification: - `npm run test --workspace=packages/desktop` - renderer store tests for allow, deny, and timeout state ### Slice 7: Settings, Auth, Model, and Mode UI - Status: pending - Goal: expose settings/auth/model/mode controls while reusing Qwen Code configuration semantics. - Files: - `packages/desktop/src/server/services/settingsService.ts` - `packages/desktop/src/server/services/runtimeService.ts` - `packages/desktop/src/renderer/stores/settingsStore.ts` - `packages/desktop/src/renderer/stores/modelStore.ts` - Acceptance criteria: - Settings writes target the existing Qwen settings locations. - Auth actions go through ACP or shared settings writer logic. - Approval mode values remain `plan/default/auto-edit/yolo`. - Verification: - `npm run test --workspace=packages/desktop` - temp HOME/QWEN_RUNTIME_DIR settings tests ### Slice 8: Packaging and Smoke Test - Status: pending - Goal: package a desktop app that can launch the bundled CLI ACP child. - Files: - `packages/desktop/electron-builder.*` - `scripts/build.js` - `scripts/copy_bundle_assets.js` - Acceptance criteria: - Packaged app starts renderer and DesktopServer. - Production ACP launch uses `ELECTRON_RUN_AS_NODE=1`. - Required CLI bundle and native/vendor resources are present. - Verification: - `npm run build` - `npm run typecheck` - desktop packaging smoke command ## Decision Log - 2026-04-25: Use a main-process hosted `DesktopServer` for MVP, matching the architecture recommendation and keeping the HTTP/WS boundary ready for a future `utilityProcess` move. - 2026-04-25: Use the latest stable Electron line available during this slice. Electron releases list Electron 41.3.0 with Node.js 24.15.0, satisfying the repository runtime requirement of Node >=20. - 2026-04-25: Implement the first server routes with Node built-ins instead of adding Express/Fastify. The current surface is small and this avoids committing to an HTTP framework before the ACP routing shape is known. - 2026-04-25: Allow CORS preflight without bearer auth, but only for allowed app origins. Actual REST requests remain bearer-token protected. ## Verification Log - 2026-04-25 Slice 1: - `npm install --ignore-scripts --workspace=@qwen-code/desktop` passed. - `npx prettier --check design/qwen-code-electron-desktop-implementation-plan.md scripts/build.js packages/desktop` passed. - `npm run lint --workspace=packages/desktop` passed. - `npm run test --workspace=packages/desktop` passed: 1 file, 4 tests. - `npm run typecheck --workspace=packages/desktop` passed. - `npm run build --workspace=packages/desktop` passed. - `npm exec --workspace=packages/desktop -- electron --version` passed: `v41.3.0`. - `npm run typecheck` passed across workspaces. - `npm run build` passed across the configured build order. Existing VS Code companion lint warnings were reported by its build script, with no errors. - 2026-04-25 Slice 2: - `npx prettier --check design/qwen-code-electron-desktop-implementation-plan.md scripts/build.js packages/desktop` passed. - `npm run test --workspace=packages/desktop` passed: 1 file, 6 tests. - `npm run lint --workspace=packages/desktop` passed. - `npm run typecheck --workspace=packages/desktop` passed. - `npm run build --workspace=packages/desktop` passed. - `npm exec --workspace=packages/desktop -- electron --version` passed: `v41.3.0`. - `npm run typecheck` passed across workspaces. - `npm run build` passed across the configured build order. Existing VS Code companion lint warnings were reported by its build script, with no errors. - 2026-04-25 Slice 3: - `npm install --ignore-scripts --workspace=@qwen-code/desktop` passed. - `npx prettier --check design/qwen-code-electron-desktop-implementation-plan.md scripts/build.js packages/desktop` passed. - `npm run test --workspace=packages/desktop` passed: 2 files, 12 tests. - `npm run lint --workspace=packages/desktop` passed. - `npm run typecheck --workspace=packages/desktop` passed. - `npm run build --workspace=packages/desktop` passed. - `npm exec --workspace=packages/desktop -- electron --version` passed: `v41.3.0`. - `npm run typecheck` passed across workspaces. - `npm run build` passed across the configured build order. Existing VS Code companion lint warnings were reported by its build script, with no errors. - 2026-04-25 Slice 4: - `npx prettier --check design/qwen-code-electron-desktop-implementation-plan.md scripts/build.js packages/desktop` passed. - `npm run test --workspace=packages/desktop` passed: 2 files, 17 tests. - `npm run lint --workspace=packages/desktop` passed. - `npm run typecheck --workspace=packages/desktop` passed. - `npm run build --workspace=packages/desktop` passed. - `npm exec --workspace=packages/desktop -- electron --version` passed: `v41.3.0`. - `npm run typecheck` passed across workspaces. - `npm run build` passed across the configured build order. Existing VS Code companion lint warnings were reported by its build script, with no errors. ## Self Review Notes - 2026-04-25 Slice 1: - Security boundary checked: renderer uses `nodeIntegration: false`, context isolation, a bundled preload whitelist, and no arbitrary IPC. - Local server checked: binds `127.0.0.1`, generates a random token by default, requires bearer auth for real routes, and rejects non-local origins. - CORS preflight intentionally remains unauthenticated but origin-gated so packaged `file://` and dev `127.0.0.1` renderers can send authorization headers. - Fixed self-review issues before completion: guarded app startup behind the Electron single-instance lock, tightened bearer parsing, and removed unused direct WebSocket dependencies until the WS slice. - 2026-04-25 Slice 2: - Runtime route remains behind the same token and origin checks as `/health`. - Runtime payload does not expose secrets; auth/account are explicit placeholders until ACP is connected. - Renderer displays runtime summary from REST only and still obtains the server token only through preload. - 2026-04-25 Slice 3: - `AcpProcessClient` follows the existing Qwen ACP boundary: `ClientSideConnection`, `ndJsonStream`, and `qwen --acp`. - The wrapper defaults permission requests to cancellation until the permission bridge slice supplies a UI-backed resolver. - Startup failures race initialize against child exit; later process exits clear connection state without leaving a rejected startup promise. - 2026-04-25 Slice 4: - Session REST routes share the same origin and bearer-token gate as health and runtime routes. - The route layer is ACP-backed through an injected client so tests cover the Qwen ACP method contracts without requiring credentials or a live model. - Electron main intentionally does not auto-start real ACP yet; CLI path resolution and packaged `ELECTRON_RUN_AS_NODE=1` behavior remain for the packaging/runtime integration slices. ## Remaining Work - Commit Slice 4. - Continue with Slice 5 WebSocket chat loop. - Continue through the ACP, session, WebSocket, permission, settings, and packaging slices until the architecture MVP is fully verified.