Originally created by: imshaikot
Closes [#26]. Stacked on [#27] (Cursor CLI), which is stacked on [#24].
Qwen Code (qwen, Alibaba, Apache-2.0) as the seventh agent the side panel can run on, in beta.
I read QwenLM/qwen-code@v0.24.4 rather than its docs, which settled the five open questions and corrected three things:
--approval-mode plan is the wrong lever. It is read-only, but it also reframes the task toward producing a plan and finishing with exit_plan_mode — wrong for a run that is supposed to act. --approval-mode default gets the same containment from the non-interactive path, which auto-denies shell/monitor/edit/write and refuses anything else that would prompt (config.ts:2060-2100, coreToolScheduler.ts:3878-3911). Passing it explicitly matters: Qwen's own default is auto, an LLM classifier call per tool call.init, not session_start. The docs sample shows the Dual Output bridge's subtype; the headless -p path emits init (nonInteractiveHelpers.ts:222).--output-format json prints an array, so answer() takes the last result element rather than parsing one object.The issue's headline claim holds: the stream is the Claude Code SDK schema, Usage is Anthropic-shaped, and the reader is a near-copy of claude.ts rather than anything Gemini-flavoured.
--mcp-config and --append-system-prompt are real flags, so this runner writes nothing to disk — no Plan.files, no per-conversation workspace, none of the §9 pitfalls Vibe, Grok and Cursor all hit.
--safe-mode is what makes that safe: it drops the user's hooks, extensions, bundled skills, settings.json MCP servers, .mcp.json and permission rules, while keeping --mcp-config as an explicit per-invocation source. It is Qwen's --strict-mcp-config and then some.
Its cost, and the one judgement call worth reviewing: --safe-mode silently ignores --core-tools, Qwen's fail-closed allowlist over its twenty-one core tools (config.ts:1934-1941). You can have the ambient-config purge or the fail-closed allowlist, not both. I took the purge — it matches the posture the rest of spawn.ts takes — and closed the built-ins with --exclude-tools deny rules instead.
A deny list cannot be fail-closed, so the init line is read back. It names every tool and MCP server that actually registered, and the reader ends the run with AGENT_UNSAFE — before the model has spoken — on a denied tool still present, a tool matching a dangerous family Browsentic never knew to name, a second MCP server, or an approval mode it did not ask for. Six tests cover that path. This is a denylist tripwire, unlike Grok's allowlist one, because Qwen's registered set is large and version-dependent and an allowlist would false-positive on every bump.
browser-use and computer-use skills. The first drives the user's real Chrome through an extension of its own — a second browser Browsentic never sees. The second runs qwen mcp add --scope user and npm install by itself on first use, mutating the user's global MCP config. Both reach the model through the skill tool, so denying it is containment, not tidiness. It costs nothing: the / picker reads SKILL.md off disk itself and never calls the CLI's skill tool..env it finds walking up from its cwd, then ~/.qwen/.env and ~/.env, for variables not already set — so a key sealEnv stripped can come back from disk. It reaches the model provider, not the model, because the shell and file tools are denied. Documented in guardrails.md rather than papered over, along with @path expansion in the prompt.QWEN_, DASHSCOPE_, BAILIAN_, OPENAI_. OPENAI_ because the documented mainstream Qwen setup is OPENAI_API_KEY + OPENAI_BASE_URL at a DashScope endpoint, and CONTAINMENT.codex already keeps it. ANTHROPIC_ and GEMINI_ are auth types Qwen accepts and this deliberately does not hand it — check() reports a Qwen with only ANTHROPIC_API_KEY as needs setup rather than promising a login that cannot happen.
Proven with a stub binary through browsentic agent: not installed → needs setup with the fix line → ready — 0.24.4 once a provider key is in the daemon's environment.
Not proven: no Qwen provider was configured on the machine this was built on, so no real turn has run. The fixtures are hand-written from the CLI's own types, per the fixtures README convention. Beta in the picker and the docs until one instruction and a follow-up turn have been through it — the same bar [#8] and [#9] were held to.
1437 tests pass (42 new in qwen.test.ts, 10 new containment tampering cases in spawn.test.ts), yarn compile and yarn daemon:compile clean, daemon coverage 70.1% against its 61% floor.
Several agent tables were already stale at five or six when Cursor landed, so they are corrected in the docs commit rather than left to drift further: limits.md, install.md, reference/cli.md, features/skills.md (now a table), internals/agent-runs.md (was missing Vibe and Cursor entirely), errors.md, SECURITY.md and the bug-report template.
Follow-up, not in here: the site repo needs public/icons/qwen.svg + qwen-dark.svg and a copy.js entry. The Cursor icons the README already points at were never added there either, so that mark is currently a broken image.
🤖 Generated with Claude Code
Tickets: #24
Tickets: #26
Tickets: #27
Tickets: #8
Tickets: #9
Ticket changed by: imshaikot