01aa4f267f feat(web-shell): make Session Workflow dependencies navigable and quiet its chrome (#10938)
* 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 at 18f9fa07, 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 on 5f70a13.

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 for d91a0f7e at 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]>
2026-09-13 13:32:32 +00:00
2025-07-22 23:26:01 +08:00
2025-08-08 20:45:54 +08:00
2025-07-22 23:26:01 +08:00
2026-09-10 15:03:19 +00:00
2025-10-23 09:27:04 +08:00
2025-12-22 21:11:33 +08:00

npm version License Node.js Version Downloads

QwenLM%2Fqwen-code | Trendshift

The open-source AI coding agent for your terminal, editor, desktop, browser, and chat.

中文 | Deutsch | français | 日本語 | Русский | Português (Brasil) | 한국어

Qwen Code in the terminal, desktop app, browser, editor, and chat, with VS Code, Zed, JetBrains, Telegram, DingTalk, WeChat, and Feishu integrations

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.

See the terminal interface

Qwen Code terminal interface

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 serve to 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

Qwen Code version 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.

Languages
TypeScript 76.5%
Java 11.5%
JavaScript 5.4%
Rust 5%
Python 0.5%
Other 0.9%