* fix(web-shell): keep the auto-sized dialog's gutter on a phone The `auto` size floors the panel at `min-w-[min(100%,560px)]` so a small plan graph does not collapse to a narrow column. tailwind-merge keeps that class alongside the base ceiling `max-w-[calc(100%-2rem)]` — different modifier groups — and below the `sm:` breakpoint min-width wins over max-width. On a 390px viewport the Plan & tasks dialog therefore rendered its rounded, ringed panel flush to both screen edges, while every fixed size (sm/md/lg/xl) kept the 1rem gutter at that same width. Floor the panel at the gutter the ceiling already reserves. * fix(web-shell): bind the DAG lane reservation to the constant that places lanes Lane spacing was stated twice in two languages: `EDGE_LANE_HEIGHT` in TS, which places each layer-spanning return lane at `lane * EDGE_LANE_HEIGHT`, and a literal `9px` in `.dagCanvas`'s bottom padding, which reserves room for them. The two are coupled only through the `--plan-edge-lanes` inline style, so raising the constant alone leaves the reservation short; with enough spanning edges the clamp pins the outer lanes together and layer-spanning edges overlap again — the exact defect the lanes exist to fix. Nothing would have caught it: the lane test mocks `offsetHeight` directly. TS now publishes the pitch as `--plan-edge-lane-height` beside the lane count, and the stylesheet reads it. Also give the graph a phone: three 240px lanes plus two 64px gutters are ~850px, so the canvas scrolled in both axes at once and the horizontal scroll hid the layer the vertical scroll was looking for. Below 720px the lane and gutter narrow. Layer semantics and the rect-measured edge routing are untouched, so this changes what fits, not what is drawn. The new stylesheet guard pins both, in the shape the other `.css.test.ts` files use, since jsdom does not compute the cascade. One test-only addition on the same component: the spanning-edge test fixed the edge's arrival column (`H 876`) but not its arrival row, and nothing else in the file asserted a spanning edge's `endY`. Substituting the return lane's `routeY` for `endY` in the path tail draws the edge ~170px below the target node while every existing assertion still passes. Pinned corner-agnostically. Refs #10866. * test(sdk): pin the skills entry in the transcript compactor keep-set `compactTaskExecutionOutput` keeps an allowlist of `task_execution` fields when it drops the bulky ones, and every neighbouring entry — `executionSummary`, `tokenCount` — is already pinned by the reducer tests. `skills` was not: removing it from the keep-set left the whole suite green, and a session restored from a persisted transcript would silently lose the skill list the live run recorded. Mutation-checked: deleting `'skills'` from the keep-set fails exactly this assertion (1 failed | 327 passed); restored, 328/328. * feat(web-shell): make workflow dependencies navigable, and let the surface state each thing once Two related passes over the Session Workflow inspector and cockpit. ## Dependencies become navigation A step's dependencies rendered as `todo.blockedBy!.join(', ')` — a run of raw Todo ids, in both the graph node and the inspector detail. An id is an address, not a label, so following an edge meant reading `step-3, step-7` and then finding those rows by eye, in a panel whose entire subject is which step waits on which. Each reference is now the step it points at — the step number the list and the graph both already show, plus its title — as a control that selects that step. The dependency list becomes the graph's navigation. Recent activity was sliced to 6 while the count beside it reported the true total, so the panel contradicted itself and the remainder was unreachable. The cap stays as a preview and gains a control that opens the rest. ## The surface states each thing once The panel had accumulated chrome that competed with its content: - Section labels were tracked-out uppercase at 10-11px, so every fixed label shouted over the step title and status it introduced. They are labels, not headlines: sentence case at a muted weight. - The cockpit spent the accent colour on a fixed "Dependency graph" eyebrow above the session title — making the one blue thing on the page the one thing that never changes, while the graph below uses that same accent to mean "this node is selected". The label moves to the subtitle line and the accent is left to mean selection. - Completed-over-total sat in a tinted pill directly above the progress bar that draws the same ratio, beside the status word that names the same state — three surfaces for one fact. The count keeps the tone as ink and drops the pill. - Runtime, tool calls, tokens and artifact kind/status were glued into single strings with middle dots, which wrapped as a unit and read out as one run of text. They are separate facts, so they are separate elements. The identity line keeps the mono face only on the short session id, which is an address the user copies or matches against a daemon log. No token or theme values change; this is the existing system applied more sparingly. Both behaviour changes are mutation-checked: reverting the dependency list to the joined id string, and restoring the silent cap, each fail exactly their new test. Refs #10866. * docs(core): say why the Todo dependency semantics are not behind the flag `todo_write`'s omitted-`blockedBy` preservation, and the `blockedBy` schema text that invites the omission, both reach sessions with Session Workflow disabled. That was a deliberate choice when the behaviour landed, but nothing in the file said so, so it reads as a missed gate — the `hasActivePlan` derivation sat bare and the only nearby comment explains the dangling-edge filter, not the gating. Record the decision and its reasoning: these are plan data-model semantics rather than presentation, and gating them on a visualization switch would give the tool two dialects, storing different plans for the same call depending on whether anyone is looking at the graph. Record the blast radius in the same place, because "reaches every session" overstates it. The merge changes an update only when the previous plan still has an unfinished item, the same id carried a non-empty `blockedBy`, and this call omits it. Plans that never used dependencies are untouched (`undefined?.filter()` is `undefined`), callers that always send the full list are unaffected, and `[]` stays the explicit clear. What does reach every session is the schema text, which is why it is named as widening this branch's exposure rather than as an independent change. Comment-only; no behaviour, no signatures. * feat(web-shell): lead the plan DAG with the step, not its status Read against the three questions a plan graph is opened with — what is running, what is blocked on what, what needs me — the node answered none of them first. **Reading order was inverted.** The node led with a status glyph, then the raw Todo id, then the status word; `nodeContent`, the only element that says what the step *is*, came third. At graph scale a reader scanned `● step-3 Blocked` before "Compare findings". An id is an address and the least useful thing at that scale, so it leaves the node face entirely — it stays in the detail panel, where it is actually copied. Content leads, at 13px/500, behind the step number the inspector list and dependency chips already use, so the three surfaces address a step identically instead of three different ways. **Status was stated three or four times per node** — glyph, status word, border tint, plus an attention badge. It is now stated once, as a 3px left rule, and only when it is worth saying: waiting is the resting state of most nodes in a real plan and carries no rule. The stylesheet already made this argument for `blocked`; extending it consistently is what stops the canvas reading as a wall of tinted boxes and gives the one running node something to stand out against. Attention folds into the same rule and outranks the status tone, because it is the state the reader is being asked to act on. Status is colour, which assistive tech cannot read, so it stays in the accessibility tree as words. **Time was invisible** in an execution trace. The node face now carries the agent count and the summed agent runtime for that step — the "is this alive or hung" signal, which is the first thing asked of a live run. Two facts with real spacing, not one middle-dot-joined string that wraps as a unit and reads out as a single run of text. **Dependencies were stated twice**, as a drawn edge and again as `Depends on step-3, step-7`. The row is now rendered only when no edge states it — above `MAX_RENDERED_PLAN_EDGES` the graph draws none, and there the row is the dependency's only statement, so deleting it outright would have lost information in exactly the case that needs it most. It renders as the same number-and-title chips the inspector uses. The input port dot goes with them: the left edge now carries the status rule, and an incoming edge already terminates in an arrowhead at the node — that arrowhead is the input marker. The output port stays, since outgoing edges leave their source unmarked. Both port dots also drop `--agent-blue-500`; every node with an edge had one, so a permanently blue dot across most of the graph left selection no colour of its own. Edge routing, the layering algorithm, the return lanes and the measure pass are untouched. This is the node face and the colour rule. Three geometry tests identified a node by its first `<span>` — the Todo id. That is now the accessible status word, so they bind to `data-plan-node-id` instead, which is the stable hook and was always the better one. Every behavioural change is mutation-checked: ungating the dependency row, deleting the accessible status word, and dropping the elapsed each fail exactly their own assertion. Design plan, including the two revisions the review pass forced, is in docs/plans/2026-09-04-session-workflow-dag-design.md. Refs #10866. * fix(web-shell): drop the unused dependencyIdsByTodo destructure ESLint flags the graph destructure binding as never used; the memo still computes and returns the map for other consumers. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-conflict/jmtlr9x6ugb * fix(web-shell): stop the activity grid from swallowing the show-all control Round-1 review findings. **Blocker.** `.showAllActivity` was a single class (0,1,0), but the button is a direct child of `.activityList`, whose base rule `.activityList > button` (0,1,1) declares the four-column activity grid and wins. The label — a bare text run that Grid wraps into an anonymous item — auto-placed into the 52px first column and wrapped to three or four lines with three empty columns beside it, and the intended padding lost too. Hover only looked right by coincidence, because the row's own hover set the same background. This is the collision handled two rules earlier for `.metrics`, missed here. Both are now pinned by the stylesheet guard so neither can regress to a bare class, and the fix resets `display` and `grid-template-columns` explicitly rather than relying on source order. **The graph's step-details panel still joined raw ids.** The node face's chips cannot be controls — that row lives inside the node's own `<button>` and a nested button is invalid HTML — but this panel sits outside it, so its references become real controls that select the step they name, which is what makes a dependency list navigation rather than text. `Depends on` and `Unblocks` both. **The narrow-viewport claim was wider than the fix.** Three 168px lanes plus two 32px gutters is ~588px, which removes horizontal scrolling between there and the 720px breakpoint but still scrolls on a 390px phone. Added a second step below 480px that gets a three-layer plan under a phone viewport, and said plainly in the comment that deeper plans still scroll — reusing the flat layout stays with #10866. The guard test is renamed to what it pins: jsdom computes no layout, so it can assert the two steps exist and narrow, not an outcome. **The lane pitch had a second copy.** The `var()` fallback restated `9px` next to the `.dagCanvas` default, so "TS owns the pitch" held only when the graph has dependencies. The default is now `0px` — the zero-lane case collapses the reservation regardless — and the guard rejects the fallback copy too. **Comment antecedent.** "the prompt change" in todoWrite named nothing a later reader could find; it now quotes the schema description it means and says why a tool schema reaches every session. Refs #10866. * fix(web-shell): keep the node's non-colour status channel, and stop clipping its port Two defects from my own review of the DAG pass, both in the class the round-1 review already caught once: invisible to jsdom, invisible to a passing unit run, and only findable by reading the stylesheet. **The status glyph was an accessibility channel, not decoration.** Making the left rule the single carrier of status dropped the glyph — but the constant it came from says exactly why it exists: "Status is carried by a glyph as well as a colour so the graph survives colour-blindness, high-contrast mode, and a greyscale screenshot." Colour plus a screen-reader-only word leaves a sighted colour-blind reader, high-contrast mode, and any greyscale capture unable to tell running from completed from needs-attention. "One status, one place" was the wrong rule; the right one is one carrier of the status *colour*, plus a redundant non-colour channel. The glyph returns for every status, in the meta line so the step's content still leads, and muted so the rule stays the only thing carrying the tone. Pinned per status, and pinned as distinct from each other, so the shape channel cannot silently collapse. **The status rule clipped the outgoing port.** Drawing it as `.node::before` needed `overflow: hidden` for the node's 8px radius to apply to it — which also clips the port dot at `right: -4px`, which is deliberately outside the box. It is a `border-left` instead: border-radius clips it natively, no overflow, and a transparent rule paints the node's own background so a resting node reads as 3px of extra left padding with no width difference between states. The stylesheet guard now rejects the overflow, the pseudo-element and a missing port offset together, since the three only fail as a set. Also drops `.nodeGlyph`'s dead sibling state: the rule is back in use, so nothing here is unused CSS. The design plan records both as corrections rather than folding them in, so a later reader sees which of its rules were wrong and why. Refs #10866. * fix(web-shell): put dependency truncation where it can actually fire Round-2 review findings that survive at this head; the round's three blockers (the node's clipped port, the show-all specificity collision, and the step-details panel's raw ids) were already fixed in the two commits after the head it reviewed. **The chips' `text-overflow: ellipsis` was dead.** It was declared on the `inline-flex` chip itself, but `text-overflow` applies to a block container's own inline content — the title sat in an anonymous flex item, so a long dependency title was clipped hard with no ellipsis. The title now has its own span carrying `overflow`, `min-width: 0` and the ellipsis, in all three places that render a reference (the node face's read-only chips and both directions of the step-details panel's links), and the stylesheet guard pins that it lives on the title rather than the box. **The comment above `.node` still described the old status behaviour** — it said status drives the accent that drives the border, which this branch stopped doing two commits ago. Rewritten to describe the border/rule split, keeping the hazard it originally recorded: with status off the border, a selected completed or blocked node can no longer pick up a second border in a clashing hue. **The compaction test's comment overclaimed.** It said every other keep-set entry is pinned by that test; it pins `skills`. Narrowed to what it does. Two coverage gaps the same round named: - The inspector test exercised only the upstream direction. Upstream and downstream render through one helper but read from different sources (`blockedBy` versus the projection's `dependentsByTodo`), so covering one did not cover the other; the downstream link is now rendered, asserted and clicked. - `planExecution.agentCount`'s singular and plural branches are separate formatter paths, and only the singular one was exercised — `1 agents` would have shipped green. Both are now pinned. Refs #10866. * fix(web-shell): drop ghost dependency controls and mark them interactive The step-details dependency buttons mapped raw selectedTodo.blockedBy, so an unresolvable step id rendered as a clickable "? <id>" control; clicking it selected a ghost id, emptied selectedTodo, and hid the details panel mid-navigation. Render from the same filtered projection the edges draw from (dependencyIdsByTodo: unknown ids, duplicates, and self-references dropped) and hide the section when nothing resolvable remains. Also add the data-plan-interactive marker to both step-details dependency button groups. The host keyboard handlers in ToolApproval and TasksStatusMessage isolate plan controls through that marker, and these were the only interactive controls in the view without it. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtmourmvi4 * fix(web-shell): let dependency titles keep their colour, and attention its ring The muted `> span` rules match every direct-child span of the chip and link — now including the title span the truncation fix added — so every dependency title rendered muted while the chip's own `color: var(--foreground)` applied to no rendered text. The title now states its own colour on the title rule, where two classes outspecific the one-class-plus-element mute. The attention rule sat before the blocked/ready rule at equal specificity, so a blocked node that also needs attention wore the muted ring instead of the attention tone. Attention is declared after the status tones so it outranks them, as its comment says. Both shapes are pinned at the source in PlanExecutionView.css.test.ts. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtn1pqbuir * fix(web-shell): dedup repeated blockedBy ids before rendering references The node-face chip row and the inspector's upstream list mapped raw model-authored blockedBy, so a repeated id emitted duplicate React keys and a duplicate chip or link. Every other blockedBy consumer already dedups with new Set, and the step-details panel reads the deduped topology projection; both surfaces now match. The dense-plan test feeds a repeated id and pins the single chip, and a new inspector test pins the single upstream link. Also pin the styles.dependencyTitle wiring on the chip and link title spans in the existing render tests: the truncation rule targets that class, so dropping the className would re-clip titles without an ellipsis while every text assertion stayed green. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtn1pqbuir * fix(web-shell): state the 480px step's true plan width in the comment The comment claimed the 480px step gets a three-layer plan under a phone viewport, but its own pinned values sum to ~396px (3×116px lanes, 2×18px gaps, 2×6px canvas padding) — still ~6px wider than the 390px phone the sibling comment names as the reference. Restate the comment with the true figure: fits phones wider than ~396px, a 390px phone still scrolls the last ~6px. Comment only; the pinned values and the stylesheet guard are unchanged. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtnaadh9j8 * docs(plans): mark the session-workflow DAG design implemented, resolve its open questions The doc shipped in this PR declared "Status: proposal, not implemented" while the same PR ships the implementation, and left two open questions the code had already answered. Mark it implemented and record the decisions where they were made: the elapsed count renders on the node face (meta line, when greater than zero), and attention folds into the left rule via data-attention, declared after every status tone so it wins the cascade. The verification note stays open — it is about process, not a design decision. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtnaadh9j8 * test(web-shell): pin the declarations the plan guards actually rest on The truncation guard now pins `overflow: hidden` beside `text-overflow: ellipsis` — the former is what makes the latter take effect, and `overflow` is not inherited from the chip. The two phone-width steps pin the lane width on its owner selector and add the paired gap/padding narrowing the fit rests on; dropping any of them restored the clipped-title or two-axis-scroll defect while the suite stayed green. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtngpuscjl * fix(web-shell): drop self-blocks in the inspector, keep status words beside attention The inspector's upstream dependency filter kept self-references: a todo listing its own id in blockedBy rendered a "Blocked by" chip pointing at itself, while the projection's dependentsByTodo builder and the DAG both drop self-blocks. Apply the same rule so the inspector agrees with every sibling surface. The node face's sr-only span also announced attention instead of the status word; an attention node lost its status from the accessibility tree while the aria-hidden glyph still carried it for sighted users. Announce both: the status word first, attention appended. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtnn5c4cjw * test(web-shell): witness the downstream click, attention word and cascade order - click the downstream 'Unblocks' reference in the step-details panel and assert the named node gains aria-pressed, so a dropped onClick in the selectedDependents block turns red - render a node into attention and assert its sr-only span carries the status word beside "Needs attention"; pin the inspector's self-block exclusion beside the dedup test - extend the cascade-order guard to all six status selectors, not just blocked, so regrouping any status tone below the attention rule fails Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtnn5c4cjw * test(web-shell): pin the plural agent count and the attention attribute - link two root Agent executions to one node and assert the face renders '2 agents', so an EN template that always emits `${count} agent` turns red; the count-1 side stays pinned by the existing assertion pair - assert the attention fixture's article carries data-attention='true', the hook the `.node[data-attention='true']` colour rule selects on; the 'Needs attention' word was pinned at18f9fa07, the attribute was not Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtntktgsk8 * test(web-shell): clear every .node-subject rule of overflow clipping The unclipped-port guard only inspected the bare `.node {}` rule, so an `overflow: hidden` on any of the other `.node`-targeting rules — a status variant, attention, selection, the `:has()` state, the media-scoped copies — would still cut the `right: -4px` output port and ship green. Walk every rule block (at-rule preludes unwrapped) and assert no selector whose subject is `.node` carries overflow clipping; descendant-subject rules and the legitimate chip/title clipping stay out of scope. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtntktgsk8 * docs(plans): state the shipped color mappings in the Color table The table claimed `--border` for every dependency line, `--status-running-fg` for running "and nothing else", and `--status-attention-fg` for needs attention "and nothing else". The shipped stylesheet draws edges with `--muted-foreground` and `--agent-blue-500` arrowheads, dresses in_progress in the running token, and dresses paused plus the step-details chips in the attention token. Record the mappings as shipped so a later palette retune reads the exclusivity clauses as they actually hold. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtntktgsk8 * fix(web-shell): close the cockpit review's rendering and a11y blockers (#10938) - Keep selection off the status rule: re-pin border-left-color to --node-rule in .node[data-selected='true'] so selecting a blocked/ready node no longer paints a 3px muted status bar. - Carry data-plan-interactive on the inspector's dependency links and the show-all-activity button, so the transcript's window-level keydown handlers early-return while they hold focus. - Let .dependencyList > li shrink (min-width: 0) so the dependency chip title's ellipsis actually engages instead of overflowing the column. - State dependencies as text whenever neither the aria-hidden edges nor the step-details panel can: above the edge budget, in the cockpit (showStepDetails={false}) and in document mode; drop self-references from the node-face chips like the topology builder does. - Give attention a shape channel: a muted visible sigil in the node meta row, since on paused nodes the data-attention rule re-declares the very token paused already wears and colour alone cannot tell them apart. - Count agents, not bare executions, in the node-face tally (nested subagents included; non-agent todo_id tools hidden), matching the node's own execution rows and the inspector's Subagents list. - Scope the visuals spec's runtime gate to the node face and the inspector detail separately; the page-wide locator matched both and threw a Playwright strict-mode violation. * fix(web-shell): keep plan arrowheads forward and name blockers (#10938) Two regressions wenshao measured in Chromium at this head, both introduced by this diff. R6-3: the 32px and 18px gutters are narrower than the edge router s fixed 24px shoulder, so the control point landed on the end point (32px tier, end tangent 0,0) or past it (18px tier, end tangent -14,0). orient=auto then flips the arrowhead back at its source, and the input port is gone, so the last direction cue pointed the wrong way at phone widths. The shoulder is now half the run: every gutter wide enough to afford the 24px floor already resolved to run / 2, so wide lanes keep their exact curve while the end tangent stays strictly positive at all three tiers. R7-3: with edges drawn and the details panel on, the node s accessible name stopped naming its blockers, because drawn edges are aria-hidden and the visible chip row is off there by design. A screen reader user had to activate every node to learn what blocks it. An sr-only summary on the same channel as the status word restores the words, using the chips step-number-plus-title labels, without bringing the visible row back. Evidence is unit level, not a browser capture: the geometry test reproduces wenshao s measured tangents exactly (0 at the 32px gutter, -14 at the 18px gutter) and goes red when the floor is restored; the accessible-name test goes red when the sr-only summary is dropped. vitest run PlanExecutionView + workflow: 9 files, 84 tests (was 81). Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtzb8sj51m * fix(web-shell): count node-face agents from the same sources the rows render R8-2: agentCount read only transcript subTools, while renderExecution also renders live child tasks from the task index, so a live child with no transcript entry rendered a row the tally never counted. Count the same deduped union the rows come from. Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): keep layer-skipping plan lanes out of the step they pass (#10938) wenshao's round-2 verification names two merge conditions on5f70a13. At <=480px the router gave every layer-skipping edge a fixed 24px shoulder. The 18px gutter leaves a 10px run, so the vertical segment landed inside the intervening lane: a dependency from step 3 to step 5 read as one into step 4, and the SVG paints under the nodes so the lane just disappeared into it. Derive each shoulder from the gutter measured on that side, the way the adjacent-layer edge already does, and halve the corner radius so the run into the arrowhead keeps a positive length. The 64px desktop tier has a 56px run, so min(24, 28) is still 24 and every desktop path stays byte-identical; the new witness pins the exact desktop path string. The node-face agent tally dedups by toolUseId exactly as the rows do, but nothing exercised the overlap. Add a fixture where a live child task and a transcript subTool share one toolUseId and assert it counts once (2 agents beside 2 rows); dropping the filter reads 3 agents. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtzjtfna1y * ci(web-shell): re-fire the head CI lanes that never scheduled The Qwen Code CI, Web-shell Visuals and review-lane runs created ford91a0f7eat 2026-09-13T08:50:29Z never scheduled a single job: a repo-wide Actions queue jam in that window left 59 runs stuck at status=queued with zero jobs, and both cancel and rerun are refused on them (cancel reports 'completed', rerun reports 'already running'). Runs created after ~09:30Z schedule normally, so these are dead rather than slow. No content change. This empty commit only re-fires pull_request:synchronize so the lanes actually run on the exact tree wenshao validated in round 3 ('merge once CI has actually run green on this head'). Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtzq8wyy28 --------- Co-authored-by: yiliang114 <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: yiliang114 <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]>
The open-source AI coding agent for your terminal, editor, desktop, browser, and chat.
中文 | Deutsch | français | 日本語 | Русский | Português (Brasil) | 한국어
Why Qwen Code?
- Agentic out of the box — Auto-Memory, Auto-Skills, SubAgents, Agent Teams, and MCP. Dynamic workflows, zero setup.
- Open-source, inside and out — The framework and the Qwen models are open-source. They evolve together. No vendor lock-in.
- Multi-protocol — Supports OpenAI, Anthropic, Gemini, and Qwen APIs. Any third-party provider or local model (Ollama / vLLM). Switch at runtime.
- Beyond the terminal — IDE plugins, Desktop app, Web UI, SDKs, and chat integrations (Telegram / DingTalk / WeChat / Feishu).
Tip
Qwen Code is actively iterating on itself — using its own agent and models to file issues, submit PRs, review code, and run tests. Powered by the community, driven by AI.
Installation
Linux / macOS:
curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.sh | bash
Windows:
irm https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.ps1 | iex
Restart your terminal after installation to ensure environment variables take effect.
NPM / Homebrew
NPM (requires Node.js 22+):
npm install -g @qwen-code/qwen-code@latest
Homebrew (macOS / Linux):
brew install qwen-code
Quick Start
Open a terminal in your project and start Qwen Code:
cd /path/to/your-project
qwen
Inside the session, run /auth to configure your provider and API key. Then try:
Explain this repository and show me where to start.
See the Authentication Guide and Settings Reference for detailed setup.
How to Use Qwen Code
Choose the interface that fits your workflow:
| Interface | Get started |
|---|---|
| Terminal | Run qwen in your project — see Quick Start. |
| Desktop | Download Qwen Code Desktop for macOS, Windows, and Linux. |
| Web | Run qwen serve --open to open the Web UI in your browser. (experimental) |
| Editor | Set up VS Code, Zed, or JetBrains. |
| Chat | Connect Telegram, DingTalk, WeChat, or Feishu, then run qwen channel start. |
For automation and custom integrations:
- Headless — Run
qwen -p "..."in scripts, CI/CD, or batch jobs. - SDKs — Build with TypeScript, Python, or Java.
- Daemon — Run
qwen serveto connect clients over HTTP + SSE (ACP). (experimental) Daemon guide.
SDK example (Python)
import asyncio
from qwen_code_sdk import is_sdk_result_message, query
async def main() -> None:
result = query(
"Summarize the repository layout.",
{
"cwd": "/path/to/project",
"path_to_qwen_executable": "qwen",
},
)
async for message in result:
if is_sdk_result_message(message):
print(message["result"])
asyncio.run(main())
Capabilities
If you know Claude Code, you already know Qwen Code — and then some. We've put significant effort into bringing Qwen Code to feature parity with Claude Code, improving both breadth and reliability across the board.
| Feature | Qwen Code | Claude Code |
|---|---|---|
| SubAgents, Agent Teams, Dynamic Workflows | ✓ | ✓ |
| Auto-Memory, Auto-Skills, Hooks | ✓ | ✓ |
| Built-in Skills (/review, /batch, /loop, /bugfix…) | ✓ | ✓ |
| MCP, Plan Mode, LSP Integration | ✓ | ✓ |
| Auto Mode, Sandbox, Git Worktrees | ✓ | ✓ |
| Computer Use (desktop automation) | ✓ | ✓ |
| IDE Plugins (VS Code / JetBrains / Zed) | ✓ | ✓ |
| SDK | ✓ | ✓ |
| Headless Mode, Session Management | ✓ | ✓ |
| Open-source — model and framework | ✓ | — |
| Multi-protocol (OpenAI / Anthropic / Gemini / Qwen + any provider) | ✓ | — |
| Agent Arena (multi-model head-to-head on same task) | ✓ | — |
Daemon Mode — qwen serve (multi-client shared agent) |
✓ | — |
| IM Channels (Telegram / DingTalk / WeChat / Feishu) | ✓ | — |
Qwen Code Evaluation
Evaluation Configuration
| Configuration | Value |
|---|---|
| Dataset | princeton-nlp/SWE-bench_Verified, 500 cases |
| Runs | 3 trials per version, 1,500 jobs per version; 7 Qwen Code versions |
| Model | Qwen 3.7 Max |
| Sampling | temperature=1, top_p=0.95 |
| Thinking / Reasoning | interleaved_thinking=true, thinking_type=adaptive, reasoning_effort=high |
| Context / Output Limits | context_1m=true, model_context_length=1,000,000, max_tokens=64,000, model_max_tokens=65,535 |
| Harness Limits | max_iterations=500, runtime_timeout_sec=7,200, agent_timeout_multiplier=3.0 |
Scores by Qwen Code Version
| Version | Avg Score | Pass Rate | pass@3 | pass^3 |
|---|---|---|---|---|
| 0.0.14 | 77.80% | 84.6% | 84.6% | 69.2% |
| 0.1.0 | 76.73% | 84.6% | 84.6% | 68.0% |
| 0.5.0 | 76.60% | 83.0% | 83.0% | 68.4% |
| 0.10.0 | 76.47% | 84.4% | 84.4% | 67.4% |
| 0.15.0 | 77.67% | 84.6% | 84.6% | 68.6% |
| 0.19.0 | 76.40% | 85.0% | 85.0% | 65.8% |
| 0.22.0 | 77.33% | 84.4% | 84.4% | 68.0% |
Avg Score Trend
Ecosystem
-
Qwen Code Desktop — Official desktop app for macOS, Windows, and Linux
-
AionUi — A modern GUI for command-line AI tools including Qwen Code
-
Gemini CLI Desktop — A cross-platform desktop/web/mobile UI for Qwen Code
-
🦞 Qwen Code Claw — Let other agents (Claude, Codex, etc.) delegate coding tasks to Qwen Code via ACP. Paste this prompt into your agent:
Use Qwen Code as a coding agent. First, fetch and read this skill reference:
https://raw.githubusercontent.com/QwenLM/qwen-code/refs/heads/main/.qwen/skills/qwen-code-claw/SKILL.md
Then follow the instructions inside to install, authenticate, and use Qwen Code via acpx for all coding tasks.
- Aliyun Model Studio CLI — Official CLI for Aliyun's AI platform (
bailian-cli). Extends Qwen Code with image/video generation, knowledge retrieval, app orchestration, and model deployment
Contributing
Contributions are welcome! See CONTRIBUTING.md for guidelines.
Acknowledgments
This project was originally based on Google Gemini CLI v0.8.2. We gratefully acknowledge the Gemini CLI team's excellent work. Starting from Qwen Code v0.1, we stopped syncing with upstream and began independent development as a multi-protocol, multi-platform agent framework with deep integrations for Qwen models and beyond.

