mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-29 12:11:09 +00:00
346 lines
16 KiB
Markdown
346 lines
16 KiB
Markdown
# 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 remains the source of truth; this file records
|
|
execution order, verification, decisions, and remaining work.
|
|
|
|
## Ground Rules
|
|
|
|
- Use Electron only; do not introduce Tauri.
|
|
- Keep Electron main thin: windows, native IPC, local server lifecycle, and ACP
|
|
process lifecycle.
|
|
- Reuse Qwen Code ACP, core configuration/auth/session/permission behavior, and
|
|
shared web UI surfaces where practical.
|
|
- Renderer must use `nodeIntegration: false`, context isolation, 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 targeted verification and a conventional
|
|
commit.
|
|
|
|
## Current Status
|
|
|
|
Slices 1-8 established the desktop package, Electron main/preload/renderer
|
|
startup, authenticated health/runtime/settings/session APIs, ACP process
|
|
wrapper, WebSocket chat loop, permission bridge, settings/model/mode controls,
|
|
packaging configuration, and package smoke verification.
|
|
|
|
Important correction from iteration 7: the previous plan text called the MVP
|
|
complete after packaging smoke, but the architecture P0 also requires project
|
|
registry, recent projects, Git status, diff review, terminal, commit flow,
|
|
desktop E2E, and Chrome DevTools renderer observability. Those items remain in
|
|
scope before a DONE marker can be created.
|
|
|
|
## Task Breakdown
|
|
|
|
### Slice 1: Desktop Workspace Skeleton and Health Service
|
|
|
|
- Status: complete
|
|
- Goal: runnable desktop package with Electron main/preload, React renderer,
|
|
and authenticated `/health`.
|
|
- Verification: desktop tests, lint, typecheck, build, root typecheck/build.
|
|
|
|
### Slice 2: Desktop Server Runtime Surface
|
|
|
|
- Status: complete
|
|
- Goal: authenticated `/api/runtime` with CLI/platform/auth summary.
|
|
- Verification: desktop tests, lint, typecheck, build, root typecheck/build.
|
|
|
|
### Slice 3: ACP Process Client Wrapper
|
|
|
|
- Status: complete
|
|
- Goal: desktop-local ACP child-process client for
|
|
`qwen --acp --channel=Desktop`.
|
|
- Verification: desktop tests, lint, typecheck, build, root typecheck/build.
|
|
|
|
### Slice 4: Session REST API
|
|
|
|
- Status: complete
|
|
- Goal: ACP-backed session create/list/load/delete/rename endpoints.
|
|
- Verification: desktop tests, lint, typecheck, build, root typecheck/build.
|
|
|
|
### Slice 5: WebSocket Chat Loop
|
|
|
|
- Status: complete
|
|
- Goal: authenticated per-session WebSocket prompt/cancel/update stream.
|
|
- Verification: desktop tests, lint, typecheck, build.
|
|
|
|
### Slice 6: Permission Bridge
|
|
|
|
- Status: complete
|
|
- Goal: route ACP permission and ask-user-question callbacks to renderer and
|
|
resolve responses with timeout cancellation.
|
|
- Verification: desktop tests, lint, typecheck, build, root typecheck/build.
|
|
|
|
### Slice 7: Settings, Auth, Model, and Mode UI
|
|
|
|
- Status: complete
|
|
- Goal: expose Qwen settings/auth/model/mode controls without returning secrets.
|
|
- Verification: desktop tests, lint, typecheck, build, root typecheck/build.
|
|
|
|
### Slice 8: Packaging and Smoke Test
|
|
|
|
- Status: complete
|
|
- Goal: package a desktop app that can launch with bundled CLI resources.
|
|
- Verification: desktop tests, lint, typecheck, build, bundle,
|
|
`package:dir`, package smoke, package launch smoke, root typecheck/build.
|
|
|
|
### Slice 9: Project Registry and Git Status
|
|
|
|
- Status: complete in iteration 7
|
|
- Goal: make opened projects first-class desktop server data and surface Git
|
|
branch/status in the workbench.
|
|
- Files:
|
|
- `packages/desktop/src/server/services/projectService.ts`
|
|
- `packages/desktop/src/server/index.ts`
|
|
- `packages/desktop/src/server/index.test.ts`
|
|
- `packages/desktop/src/server/types.ts`
|
|
- `packages/desktop/src/renderer/api/client.ts`
|
|
- `packages/desktop/src/renderer/App.tsx`
|
|
- `packages/desktop/src/renderer/styles.css`
|
|
- Acceptance criteria:
|
|
- `GET /api/projects` returns recent projects from a Qwen global desktop
|
|
store.
|
|
- `POST /api/projects/open` validates a directory, persists it as recent,
|
|
and returns name/path/branch/status.
|
|
- `GET /api/projects/:id/git/status` refreshes Git status for a registered
|
|
project.
|
|
- Renderer Open Project flow registers the selected directory through the
|
|
server, lists recent projects, scopes thread creation/listing to the active
|
|
project, and shows branch/status in the top bar and review panel.
|
|
- Verification:
|
|
- `npm run test --workspace=packages/desktop`
|
|
- `npm run typecheck --workspace=packages/desktop`
|
|
- `npm run lint --workspace=packages/desktop`
|
|
- `npm run build --workspace=packages/desktop`
|
|
- E2E coverage:
|
|
- Record in
|
|
`.qwen/e2e-tests/electron-desktop/project-git-status.md`.
|
|
- Later Playwright Electron coverage must use a temporary Git workspace,
|
|
choose it through the preload dialog hook/fake native bridge, assert recent
|
|
project visibility, branch/status chips, and no black screen.
|
|
|
|
### Slice 10: Renderer Asset Loading and CDP Port
|
|
|
|
- Status: complete in iteration 7
|
|
- Goal: make the built renderer load reliably from Electron main and expose a
|
|
local-only Chrome DevTools Protocol endpoint when explicitly requested for
|
|
tests/debugging.
|
|
- Files:
|
|
- `packages/desktop/src/main/main.ts`
|
|
- `packages/desktop/src/main/lifecycle/remoteDebugging.ts`
|
|
- `packages/desktop/src/main/lifecycle/remoteDebugging.test.ts`
|
|
- `packages/desktop/src/main/windows/MainWindow.ts`
|
|
- `packages/desktop/vite.config.ts`
|
|
- Acceptance criteria:
|
|
- `QWEN_DESKTOP_CDP_PORT=<port>` appends Electron remote debugging switches
|
|
for `127.0.0.1` and the requested numeric port only.
|
|
- Invalid ports do not enable remote debugging.
|
|
- Built renderer assets use relative URLs so `file://.../dist/renderer` can
|
|
load CSS/JS.
|
|
- MainWindow resolves preload and renderer paths correctly from
|
|
`dist/main/windows`.
|
|
- Verification:
|
|
- `npm run test --workspace=packages/desktop`
|
|
- `npm run typecheck --workspace=packages/desktop`
|
|
- `npm run lint --workspace=packages/desktop`
|
|
- `npm run build --workspace=packages/desktop`
|
|
- `QWEN_DESKTOP_CDP_PORT=9339 npm run start --workspace=packages/desktop`
|
|
exposed `http://127.0.0.1:9339/json/version` and `/json/list`; process was
|
|
terminated after endpoint verification.
|
|
- E2E coverage:
|
|
- Record in
|
|
`.qwen/e2e-tests/electron-desktop/cdp-renderer-observability.md`.
|
|
- Later Playwright/DevTools MCP coverage must connect to the page websocket,
|
|
assert DOM text and console/network health, and save a screenshot.
|
|
|
|
### Slice 11: Workspace Review Shell
|
|
|
|
- Status: pending
|
|
- Goal: split the renderer into explicit TopBar, ProjectSidebar, ThreadList,
|
|
ChatThread, ReviewPanel, and TerminalDrawer components while preserving the
|
|
current server-backed project/session/chat/settings behavior.
|
|
- Acceptance criteria:
|
|
- First workspace viewport visibly contains top bar, project/thread sidebar,
|
|
central thread, right review tabs, and bottom terminal drawer structure.
|
|
- No renderer Node access or IPC broadening.
|
|
- Existing desktop tests and typecheck still pass.
|
|
- E2E coverage:
|
|
- Launch renderer with fake server/preload data and assert layout landmarks.
|
|
|
|
### Slice 12: Diff Review and Commit
|
|
|
|
- Status: partial complete in iteration 7
|
|
- Goal: add Git diff/status review APIs and UI actions for accept/revert and
|
|
commit.
|
|
- Files:
|
|
- `packages/desktop/src/server/services/gitReviewService.ts`
|
|
- `packages/desktop/src/server/index.ts`
|
|
- `packages/desktop/src/server/index.test.ts`
|
|
- `packages/desktop/src/server/services/projectService.ts`
|
|
- `packages/desktop/src/renderer/api/client.ts`
|
|
- `packages/desktop/src/renderer/App.tsx`
|
|
- `packages/desktop/src/renderer/styles.css`
|
|
- Acceptance criteria:
|
|
- Right Changes tab shows changed files and unified diff.
|
|
- Stage/unstage/revert/commit routes are token protected and scoped to a
|
|
registered project.
|
|
- Commit errors are visible in the UI.
|
|
- Completed:
|
|
- Token-protected diff, stage, revert, and commit routes scoped to registered
|
|
projects.
|
|
- Basic right Review panel changed-file list, textual diff preview, Stage
|
|
All, Revert All, commit message input, and Commit action.
|
|
- Server tests for diff, stage, commit, revert, invalid project path, and Git
|
|
status metadata.
|
|
- Remaining:
|
|
- Hunk-level accept/revert, inline comments, Open in Editor, richer file tree,
|
|
and renderer E2E coverage.
|
|
- E2E coverage:
|
|
- Record in `.qwen/e2e-tests/electron-desktop/diff-review-commit.md`.
|
|
- Later Electron E2E must use a temporary Git workspace with a fake file
|
|
change, accept/stage, commit, and error diagnostics.
|
|
|
|
### Slice 13: Scoped Terminal
|
|
|
|
- Status: partial complete in iteration 7
|
|
- Goal: add a current-project/current-thread terminal drawer with spawn, output,
|
|
clear, kill, and send-output-to-AI plumbing.
|
|
- Files:
|
|
- `packages/desktop/src/server/services/terminalService.ts`
|
|
- `packages/desktop/src/server/index.ts`
|
|
- `packages/desktop/src/server/index.test.ts`
|
|
- `packages/desktop/src/renderer/api/client.ts`
|
|
- `packages/desktop/src/renderer/App.tsx`
|
|
- `packages/desktop/src/renderer/styles.css`
|
|
- Acceptance criteria:
|
|
- Terminal cwd is constrained to the active project.
|
|
- Terminal output is visible and copyable; kill and clear work.
|
|
- Agent command permission remains separate from user terminal execution.
|
|
- Completed:
|
|
- Token-protected terminal run/get/kill routes resolve cwd from the
|
|
registered project id.
|
|
- Bottom drawer runs project-scoped commands, polls output while running,
|
|
supports Kill and Clear, and does not use renderer Node APIs.
|
|
- Server tests cover command output and killing a running command.
|
|
- Remaining:
|
|
- Interactive PTY resize/write, output selection/copy polish, send output to
|
|
AI, terminal tabs/history, and Electron renderer E2E.
|
|
- E2E coverage:
|
|
- Record in `.qwen/e2e-tests/electron-desktop/terminal-drawer.md`.
|
|
- Later Electron E2E must run a harmless command in a temporary workspace and
|
|
assert output appears in the drawer.
|
|
|
|
### Slice 14: Desktop E2E Harness
|
|
|
|
- Status: pending
|
|
- Goal: add repeatable Electron E2E harness with fake ACP, temporary HOME and
|
|
workspace, screenshot/console/network diagnostics, and CDP renderer access.
|
|
- Acceptance criteria:
|
|
- `QWEN_DESKTOP_CDP_PORT` is used by the harness to inspect the renderer on
|
|
`127.0.0.1`.
|
|
- E2E asserts first screen is not black, service is connected, project open
|
|
works, thread creation works, permission response works, settings save
|
|
works, and package smoke still passes.
|
|
- Failures write screenshots, console errors, failed requests, and main logs
|
|
under `.qwen/e2e-tests/electron-desktop/`.
|
|
|
|
## Decision Log
|
|
|
|
- 2026-04-25: Use a main-process hosted `DesktopServer` for MVP, matching the
|
|
architecture recommendation while keeping HTTP/WS boundaries explicit.
|
|
- 2026-04-25: Use Electron 41.3.0, whose embedded Node satisfies the repository
|
|
runtime requirement.
|
|
- 2026-04-25: Use Node built-in HTTP for the current server surface instead of
|
|
adding Express/Fastify.
|
|
- 2026-04-25: Keep ACP update normalization inside `packages/desktop` until the
|
|
desktop protocol stabilizes.
|
|
- 2026-04-25: Package the root `dist/` bundle as `resources/qwen-cli` and
|
|
launch it with `ELECTRON_RUN_AS_NODE=1` in packaged apps.
|
|
- 2026-04-25: Store desktop recent projects in the Qwen global directory as
|
|
`desktop-projects.json`, separate from `settings.json`, because it is app UI
|
|
state rather than model/auth configuration.
|
|
- 2026-04-25: Use `git status --porcelain=v1 --branch` for Slice 9 instead of
|
|
introducing a desktop `simple-git` dependency. This keeps the server surface
|
|
small and returns conservative status metadata for both clean and dirty repos.
|
|
- 2026-04-25: Keep the CDP switch opt-in through `QWEN_DESKTOP_CDP_PORT` and
|
|
always pair it with `remote-debugging-address=127.0.0.1`; production remains
|
|
closed unless the environment variable is set.
|
|
- 2026-04-25: Implement desktop Git review with `git` via `execFile` and
|
|
explicit relative path validation. This avoids broad shell execution and
|
|
keeps review operations scoped to projects registered through the desktop
|
|
project service.
|
|
- 2026-04-25: Start the scoped terminal as a project-bound command runner
|
|
rather than an interactive PTY. This gives the renderer verifiable output and
|
|
kill behavior now while leaving PTY write/resize and send-output-to-AI as the
|
|
next terminal refinement.
|
|
|
|
## Verification Log
|
|
|
|
- 2026-04-25 Slices 1-8:
|
|
- Prior iterations passed desktop tests, lint, typecheck, build, root
|
|
typecheck/build, bundle, package dir, package smoke, and package launch
|
|
smoke. Electron-builder warnings were non-fatal metadata/signing warnings.
|
|
- 2026-04-25 Slice 9:
|
|
- `npm run test --workspace=packages/desktop` passed: 7 files, 45 tests.
|
|
- `npm run typecheck --workspace=packages/desktop` passed.
|
|
- `npm run lint --workspace=packages/desktop` passed.
|
|
- `npm run build --workspace=packages/desktop` passed.
|
|
- 2026-04-25 Slice 10:
|
|
- `npm run test --workspace=packages/desktop` passed: 8 files, 48 tests.
|
|
- `npm run typecheck --workspace=packages/desktop` passed.
|
|
- `npm run lint --workspace=packages/desktop` passed.
|
|
- `npm run build --workspace=packages/desktop` passed.
|
|
- `curl --fail --silent http://127.0.0.1:9339/json/version` passed while the
|
|
app was launched with `QWEN_DESKTOP_CDP_PORT=9339`.
|
|
- `curl --fail --silent http://127.0.0.1:9339/json/list` passed and returned
|
|
a `Qwen Code` page at
|
|
`file:///Users/dragon/Documents/qwen-code/packages/desktop/dist/renderer/index.html`.
|
|
- 2026-04-25 Slice 12 basic diff review:
|
|
- `npm run test --workspace=packages/desktop` passed: 8 files, 50 tests.
|
|
- `npm run typecheck --workspace=packages/desktop` passed.
|
|
- `npm run lint --workspace=packages/desktop` passed.
|
|
- `npm run build --workspace=packages/desktop` passed.
|
|
- 2026-04-25 Slice 13 basic scoped terminal:
|
|
- `npm run test --workspace=packages/desktop` passed: 8 files, 52 tests.
|
|
- `npm run typecheck --workspace=packages/desktop` passed.
|
|
- `npm run lint --workspace=packages/desktop` passed.
|
|
- `npm run build --workspace=packages/desktop` passed.
|
|
|
|
## Self Review Notes
|
|
|
|
- Slice 9 keeps project registration behind the existing bearer-token and
|
|
origin gates.
|
|
- Project open validates that the path exists and is a directory before
|
|
persisting it.
|
|
- Git status failures are non-fatal and render as non-repository metadata,
|
|
avoiding a broken workspace for projects without Git.
|
|
- Renderer still obtains the token only from preload and does not gain Node
|
|
integration.
|
|
- Slice 10 keeps remote debugging off by default, rejects non-numeric/out of
|
|
range ports, and binds only to loopback when enabled.
|
|
- The CDP smoke verified endpoint discovery but did not yet drive DOM,
|
|
console, network, or screenshot assertions through MCP; that remains in the
|
|
E2E harness slice.
|
|
- Slice 12 review operations resolve the project path from the registered
|
|
project id server-side; renderer cannot submit arbitrary cwd values.
|
|
- File-scoped Git operations reject absolute paths and parent-directory
|
|
traversal. The current UI exposes all-scope operations only; file/hunk UI is
|
|
still pending.
|
|
- Revert All uses `git restore` and `git clean -fd`, so it is intentionally
|
|
available only as an explicit user review action and remains scoped to the
|
|
active registered project.
|
|
- Slice 13 terminal commands resolve cwd from the active registered project id
|
|
on the server. The renderer never sends an arbitrary cwd.
|
|
- The current terminal is a command runner, not a full PTY. It does not bypass
|
|
agent tool permission because agent shell execution still flows through ACP
|
|
and core permissions.
|
|
|
|
## Remaining Work
|
|
|
|
- Implement explicit componentized workspace shell, hunk-level diff review,
|
|
terminal PTY/write/send-output-to-AI refinements, Electron E2E harness,
|
|
DevTools MCP DOM/console/network/screenshot checks, and final package smoke
|
|
before creating the DONE marker.
|