Skip to content

Migration tracker: re-found OpenWork on the Qwen Code Web Shell + Tauri architecture #80

Description

@yiliang114

Summary

Re-found OpenWork on the Qwen Code stack: fork QwenLM/qwen-code into a standalone OpenWork repository, adopt the Tauri 2 desktop shell (packages/desktop-shell) and Web Shell UI (packages/web-shell), and port OpenWork's existing product surface onto that architecture.

This issue is the master tracker. Workstreams below will be broken out into sub-issues.


1. Context: what OpenWork actually is today

Establishing this correctly matters, because it determines what "sync from upstream" can and cannot mean.

OpenWork is not a fork of Qwen Code. Its root commit 7b57e21 (2026-05-01) is a subtree extraction from eb45067f "Make backend Qwen-only", whose tree is the root-level Craft layout (apps/{cli,electron,viewer,webui}, Dockerfile.server). Qwen Code did not gain packages/desktop until 62e45c567 (#3778) on 2026-06-11 — six weeks later. eb45067f is not an ancestor of qwen-code@main.

The real topology is two siblings from a common Craft-derived ancestor:

craft-ai-agents/craft-agents-oss   (craft-agent, Electron)
            │
            ▼
   eb45067f "Make backend Qwen-only"  (2026-05-01)
            │
    ┌───────┴────────┐
    ▼                ▼
 OpenWork      qwen-code/packages/desktop
 228 commits    50 commits
 v0.1.4          v0.0.5

Current coupling to Qwen Code is narrow: qwenCodeRuntime.version: 0.15.11 in package.json, vendored from the npm tarball by scripts/vendor-qwen-code.ts, spawned as a subprocess by packages/shared/src/agent/qwen-agent.ts with ['--acp', '--channel=desktop']. Upstream CLI is now at 0.21.5 — six minors ahead.

Consequence of this migration: tracking craft-agents-oss ends. Adopting Web Shell replaces the Craft-derived UI and server layers. That is an accepted, deliberate trade — noted here so it is never a surprise.


2. Target architecture

Mirrors upstream's packages/desktop-shell:

  • Tauri 2 shell (~1,537 lines of Rust: main.rs 678, runtime.rs 547, desktop_state.rs 312) replaces Electron.
  • Shell starts the daemon on an ephemeral loopback port with a per-launch bearer token, polls /health, then opens the daemon-served Web Shell in a native window.
  • scripts/prepare-runtime.js stages runtime/qwen-code/ with a platform Node.js, the built CLI, and Web Shell under lib/web-shell/.

All artifacts build from source. prepare-runtime.js resolves sourceRoot from QWEN_CODE_ROOT or the repo root and runs npm run build -- --cli-only, --workspace=packages/webui, --workspace=packages/web-shell. No @qwen-code/* package is fetched from npm. This is deliberate — @qwen-code/web-shell is unpublished, and @qwen-code/webui on npm is 0.1.0-beta.4 against a monorepo at 0.21.5. Only third-party deps come from the registry.


3. Open questions — resolve before Phase 2

These are genuine unknowns, not rhetorical. Each can change scope materially.

  • Browser panes / CDP. apps/electron/src/main/browser-cdp.ts and browser-pane-manager.ts depend on Electron BrowserView + Chrome DevTools Protocol, backing the agent browser tools in packages/shared/src/agent/browser-tools.ts. Tauri has no BrowserView equivalent; WebKitGTK exposes no CDP. This may be architecturally blocked. Spike before committing.
  • Web Shell variant seam. client/customization.tsx covers only presentation (markdown components, code-block/chart renderers, WelcomeHeader, theme); client/extensions/ contains one module. There is no seam for pages, navigation, or settings sections — which is most of what OpenWork adds. Decide whether to build that seam upstream first (strongly preferred) or patch Web Shell source directly.
  • Session/credential data migration. OpenWork stores ~/.craft-agent/{config,config-defaults,credentials,docs}. Session formats differ entirely from daemon transcripts, and macOS Keychain entries are bound to the old app identity. Decide: migrate, or ship a documented one-way break.
  • Feature freeze policy — the parity target is currently moving. OpenWork is still shipping actively (12 commits in W30; 17 UI features in the last ~30 commits, driven by the autonomous feature loop bootstrapped in chore(loop): bootstrap autonomous desktop-feature loop infra #38). If that continues through a multi-month migration, every new Electron feature adds to Phase 4's port backlog and parity recedes as it is approached. Decide one of: (a) freeze feature work on the Electron app at a named commit, (b) require new features to land on the new stack only once Phase 3 completes, or (c) accept dual-maintenance and staff for it explicitly. This must be answered before Phase 4 begins — it is the difference between a finite port and an open-ended one.

4. Phase plan

Phase 0 — De-risk (no migration work)

  • Bump qwenCodeRuntime.version 0.15.110.21.5; re-vendor; validate ACP. Note: OpenWork is on @agentclientprotocol/sdk ^0.21.0 while qwen-code 0.21.5 CLI is on ^0.14.1 — verify PROTOCOL_VERSION negotiation rather than assuming. Confirm --acp (not deprecated --experimental-acp) and --channel. Valuable independently of migration.
  • Browser-pane / CDP spike (see §3).
  • Run bun run server:prod and exercise the app in a plain browser. Whatever breaks is the true Electron-coupling inventory — 95 of 509 renderer files import window.electron / electron directly.
  • Build desktop-shell unmodified on macOS/Windows/Linux; measure real bundle size and memory before promising numbers (runtime/qwen-code/ ships Node + CLI, so the Chromium saving is partly offset).

Phase 1 — Repository foundation

  • Freeze current tree as modelstudioai/openwork-legacy (reference during porting — do not disturb the shipping product).
  • Create new OpenWork repo from QwenLM/qwen-code.
  • First commit: neutralize CI. 43 workflows, 13 release/publish. release.yml runs on daily cron; release-vscode-companion.yml fires on release: published. Also release-sdk*.yml, build-and-publish-image.yml, sync-release-to-oss.yml. Scheduled workflows are auto-disabled only in true GitHub forks, not plain repos.
  • Establish branch model: vendor/upstream (pristine mirror, never edited) → main.
  • .gitattributes: package-lock.json merge=ours, regenerate post-merge.
  • Keep all upstream packages (~18 + 10 channels). Do not prune — deleting files conflicts on every future merge.
  • Preserve upstream LICENSE/NOTICE; append attribution rather than replacing (Apache-2.0 requirement). Clean the stale Craft-era Anthropic Commercial Terms clause, which no longer applies post-eb45067f.

Phase 2 — Shell and branding

  • Fork desktop-shell → OpenWork shell with own identifier, icons, signing identity, updater keypair, endpoint.
  • Overlay, do not edit tauri.conf.json (currently com.alibaba.qwen-code, pubkey, and a QwenLM/qwen-code updater endpoint). Use tauri build --config tauri.openwork.conf.json deep-merge; verify merge semantics for the pinned Tauri version.
  • Release-time assertion that the shipped updater endpoint and identifier are OpenWork's. Shipping with Qwen's endpoint would push users into a different application — unrecoverable, so it must be enforced mechanically.
  • Add desktop-release.yml as a new file, not an edit to upstream's.

Phase 3 — Variant seam

  • Land page/route registry, settings-section registry, slot-based navigation in Web Shell (upstream where possible).
  • Establish packages/web-shell/client/openwork/ and packages/openwork-* for additive product code.
  • Start PATCHES.md ledger recording every intentional divergence in shared files, with rationale.

Phase 4 — Feature port

Port against the parity matrix (§5), one sub-issue per cluster.

Phase 5 — Data migration

  • Settings, session history, credentials (Keychain re-binding).
  • Electron → Tauri update bridge. Upstream's electron_bridge handles update delivery only — data migration is separate and ours to build.

Phase 6 — Release

  • Platform QA, signing/notarization, staged rollout, rollback plan.

5. Preservation inventory — what must not be lost

Nothing here has a Web Shell equivalent unless noted. Each needs an explicit port / rebuild / drop decision recorded in the matrix.

Settings surface (20 pages): Ai, Appearance, App, Extensions, General, Hooks, Input, Labels, McpServers, Memory, Messaging, Permissions, Preferences, Qwen, Server, Shortcuts, Workspace, plus SettingsNavigator and searchable filtering.

Pages: ChatPage, DraftChatPage, PreferencesPage, ShortcutsPage, SkillInfoPage, SourceInfoPage.

Feature domains (per apps/electron/resources/docs/): automations, browser-tools, data-tables, html-preview, image-preview, labels, llm-tool, mermaid, pdf-preview, permissions, skills, sources, statuses, themes, tool-icons.

Doc tools (Python, stack-independent — needs new sidecar packaging): pdf_tool, xlsx_tool, docx_tool, pptx_tool, img_tool, ical_tool, doc_diff, markitdown_cli.

Packages: messaging-gateway + messaging-whatsapp-worker (upstream has 10 channels but no WhatsApp — consider rebuilding against packages/channels/base plugin API and upstreaming), session-tools-core, session-mcp-server, apps/viewer (transcript sharing).

Recent UI work — 13 features shipped in the last ~30 commits, highest regression risk because it is newest and least documented: starter prompt suggestions (#73), interface zoom ⌘+/⌘-/⌘0 (#69), increase contrast (#67), chat text size (#65), conversation width (#63), live word/char count (#61), shortcuts search (#59), command palette recents (#57), thinking-menu shortcut (#55), prompt-history recall (#53), composer expand/collapse (#49), jump-to-latest (#47), copy-as-Markdown (#71), reduce motion (#51), thinking-level picker (#45), command palette ⌘K (#42), settings navigator search (#40).

Cross-cutting: i18n catalogs + lint:i18n:parity tooling (Web Shell has its own i18n.tsx — two systems to reconcile), Sentry wiring, voice, network-proxy.ts (Electron session proxy API — no Tauri equivalent), pets registry.

Retired by design: apps/electron main/preload/renderer (~119k LOC), packages/ui (30k), packages/server-core (35k), most of packages/shared's agent loop (95k).


6. Risks

# Risk Severity Mitigation
1 Product-shape divergence. Web Shell is coding-tool shaped (GitBranchIndicator, GitModePopover, BranchPickerPopover, artifacts/, agents/); OpenWork is knowledge-work shaped (Sources, Skills, Labels, Memory, Messaging). Pressure to add if (product === …) into shared code. High Phase 3 seam before Phase 4
2 Browser panes may be unportable (§3). High Spike in Phase 0
3 Data/credential loss on upgrade. Keychain bound to old identity; incompatible session formats. High Phase 5; treat as release blocker
4 Updater cross-contamination. High (low likelihood, unrecoverable impact) Generated config + release assertion
4b Inherited upstream defaults. The fork carries qwen-code's endpoint and telemetry plumbing: dashscope.aliyuncs.com / -intl / -us, coding.dashscope.aliyuncs.com/v1, gateway.alibaba-inc.com/dashscope/v1, a CLEARCUT path, and privacy.usageStatisticsEnabled handling. Shipping OpenWork on these routes user traffic and usage data to Alibaba infrastructure. Same hazard class as #4 and easy to miss, because nothing fails visibly. High Full audit in Phase 2; release assertion on shipped endpoints + telemetry defaults
4c Moving parity target (§3). Feature work continuing on the Electron app during migration grows the Phase 4 backlog continuously. High Freeze policy decided before Phase 4
5 Toolchain collision. OpenWork is bun; qwen-code is npm, and prepare-runtime.js hard-requires it (npm_execpath is unavailable. Run through npm.). Medium Decide convergence explicitly
6 Engine differences. WebView2 / WebKitGTK vs bundled Chromium. Web Shell leans on Shiki, KaTeX, mermaid, echarts, CodeMirror, and shadowDom.ts. Medium Budget Linux QA or scope out of v1
7 Release-train coupling. Web Shell is versioned in lockstep with the monorepo (0.21.5). Medium Document branching/cherry-pick policy
8 Merge debt accumulation. Upstream ships ~2 minors/month; conflict cost scales worse than linearly with drift. Medium Monthly cadence, never skip
9 Attribution. Craft-derived code moving between trees carries Apache-2.0 notice obligations; Craft ships TRADEMARK.md. Low Legal pass in Phase 1
10 Perf/size expectations set too high. Low Measure in Phase 0 before committing publicly

7. Areas requiring special attention

  • Never reformat or reorganize upstream files. A single prettier run across packages/web-shell would poison every future merge.
  • Additive files never conflict. Prefer new files over in-place edits; when editing upstream files, insert a call into our module rather than inlining logic.
  • Upstream everything generic. Since the same team maintains both sides, a fix landed upstream permanently leaves our patch set — the single most effective conflict reducer available.
  • The 95 renderer files touching window.electron are the concrete Electron-coupling inventory; web-api.ts (277 lines) and the preload (421 lines) define the contract to re-implement.
  • Web Shell is 182,393 LOC — larger than OpenWork's current renderer (103,203). Porting to it is the bigger codebase absorbing the smaller.
  • Audit every inherited default, not just visible branding. Model/API endpoints, auth flows, telemetry targets, crash reporting, update endpoints, and analytics all arrive preconfigured for Qwen Code. Branding is what you see; endpoints are what ships. Treat this as a checklist with an automated assertion, not a review pass.
  • Security posture is inherited too. The daemon binds loopback with a per-launch bearer token, and Web Shell receives that token via URL fragment (#token=, never sent to the server). tauri.conf.json ships a restrictive CSP. Any OpenWork addition that widens the network surface — notably the messaging gateways and the WhatsApp worker — must be reviewed against that model rather than assuming it still holds.
  • Crash reporting needs rebuilding, not porting. @sentry/electron has no meaning post-migration; only @sentry/react carries over, and main-process crash capture needs a Tauri-native equivalent.

8. Upstream synchronization strategy

QwenLM/qwen-code  main
       │ fetch (never edit)
       ▼
vendor/upstream        pristine mirror branch
       │ merge at release tags only
       ▼
main                   OpenWork product branch
git remote add upstream https://github.com/QwenLM/qwen-code
git fetch upstream --tags
git checkout -b vendor/upstream upstream/main

# each sync:
git checkout vendor/upstream && git merge --ff-only v0.22.0
git checkout main && git merge vendor/upstream
npm install && git add package-lock.json
  • The pristine branch provides a stable three-way merge base and separates "what changed upstream" from "what we changed" — the question actually asked during conflict resolution.
  • Merge release tags, not main. Bounded, tested diffs.
  • Monthly cadence. Three minors behind costs far more than three times one minor.
  • PATCHES.md ledger so that, months later, a resolver knows whether a divergent line was deliberate.

9. Validation & acceptance criteria

Feature parity (blocking):

  • Parity matrix complete: every item in §5 marked ported / rebuilt / consciously dropped, with sign-off on each drop.
  • All 20 settings pages reachable with equivalent function.
  • All 17 recent UI features (§5) verified by hand — newest code, weakest coverage.
  • Doc tools pass existing smoke suites (test:doc-tools).
  • i18n parity lint passes across merged catalogs; no untranslated regressions.

Data integrity (blocking):

  • Upgrade from latest Electron build preserves settings, session history, and credentials with zero re-authentication.
  • Rollback path validated.

Release safety (blocking):

  • Automated assertion: shipped identifier + updater endpoint are OpenWork's, on every platform artifact.
  • Automated assertion: no Alibaba/DashScope endpoint or CLEARCUT telemetry target is reachable from a default OpenWork install; telemetry and usage-statistics defaults match OpenWork's stated privacy policy.
  • Crash reporting verified end-to-end from both the Rust shell and the web layer.
  • Signing and notarization green on macOS and Windows.

Platform:

  • macOS, Windows verified. Linux verified or explicitly descoped with a written decision.

Performance (measured, not assumed):

  • Bundle size and cold-start vs current Electron build, recorded before/after.

Sync process:

  • One full practice merge from an upstream release tag completed, conflicts resolved, time recorded as the cadence baseline.

10. Sub-issue breakdown

  1. Runtime bump 0.15.110.21.5 + ACP validation (Phase 0, independent value)
  2. Browser-pane / CDP portability spike (Phase 0, potential blocker)
  3. Browser-context audit via server:prod (Phase 0)
  4. Baseline bundle/perf measurement (Phase 0)
  5. Repo foundation + CI neutralization (Phase 1)
  6. Shell fork, branding, updater safety (Phase 2)
  7. Web Shell variant seam (Phase 3 — gates Phase 4)
  8. Feature port: settings surface (Phase 4)
  9. Feature port: Sources / Skills / Labels / Memory (Phase 4)
  10. Feature port: doc tools + sidecar packaging (Phase 4)
  11. Feature port: messaging + WhatsApp as channel plugin (Phase 4)
  12. Data migration: settings, sessions, credentials (Phase 5)
  13. Inherited-defaults audit: endpoints, telemetry, auth, crash reporting (Phase 2, release blocker)
  14. Platform QA + release pipeline (Phase 6)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions