Find a file
Shaojin Wen f5bef6c5dd
feat(cli): replace inline AgentExecutionDisplay with always-on LiveAgentPanel (#3909)
* feat(cli): replace inline AgentExecutionDisplay with always-on LiveAgentPanel

Surface running subagents in a borderless, always-on roster anchored
beneath the input footer (mirrors Claude Code's CoordinatorTaskPanel)
and retire the verbose inline `AgentExecutionDisplay` frame whose
per-tool-call mutations caused scrollback flicker. Detail / cancel /
resume keep flowing through the existing BackgroundTasksDialog.

LiveAgentPanel:
- Two-column row: status icon + (optional) type + description +
  activity on the left (truncate-end), elapsed + tokens on the right
  in a flex-shrink:0 column so the cost/time fields are never hidden
  by long descriptions.
- Re-pulls each agent from BackgroundTaskRegistry on every wall-clock
  tick so `recentActivities` stays fresh — the snapshot from
  `useBackgroundTaskView` only refreshes on `statusChange` to keep
  the footer pill / AppContainer quiet under heavy tool traffic.
- Reaches for Config via raw ConfigContext (not useConfig) so the
  panel degrades to snapshot-only when no provider is mounted (test
  isolation).
- Hides when any dialog is visible (auth / permission / bg tasks)
  and self-hides when no agent entries are live.
- Drops `subagentType` from the row when it is the default
  `general-purpose` builtin to keep the line uncluttered; specialized
  types still bold-anchor the row.
- Keeps terminal entries on screen for 8s so the user gets feedback
  when an agent finishes, then they fall off (BackgroundTasksDialog
  retains them long-term).

Inline frame retirement:
- ToolMessage's SubagentExecutionRenderer collapses to the focus-
  routed approval surfaces only (focus-holder banner + queued
  marker). All other agent state is owned by the panel + dialog.
- AgentExecutionDisplay.tsx + test removed (-918 lines); the
  subagents/index export is dropped with a pointer to the new
  surfaces.

Net diff: +97 / -1069.

* fix(cli): move elapsed + tokens to the front of LiveAgentPanel rows

The two-column layout used `flex-grow:1` on the left description
column, which puffed it out to fill the row even when content was
short — leaving a visible gap between the description tail and the
right-pinned elapsed/tokens whenever the terminal was wider than the
content. Worse, the gap made it look like display space was being
wasted while the description still got truncated.

Move elapsed + tokens to the front of the row (right after the status
icon) so:

- Time and cost are pinned at a stable left position and are NEVER at
  risk of being truncated, regardless of description / activity length.
- The row reads as one tight left-to-right line — no flex-grow, no
  internal gap. On wide terminals the unused width sits at the row
  tail (invisible), where it belongs.
- Description + activity become the truncatable tail; `truncate-end`
  cuts only when the line genuinely overflows the panel width.

Also wrap the icon-plus-spaces span in a template literal so the two
spaces of breathing room after the glyph survive a prettier pass.

Verified at 60 / 100 / 200 cols: at 200 cols the row renders flush
with no trailing ellipsis and no internal gap; at 60 cols the time +
tokens stay at the front and the description tail truncates with `…`.

* fix(cli): switch LiveAgentPanel row to layout C (right-pinned, no flex-grow)

Iterating on the row layout based on visual review:

- Layout A (time first, single Text) put numbers ahead of identity,
  which broke the natural left-to-right reading order.
- Layout B (right-pinned with flex-grow:1 on left) puffed the left
  column out to fill the row, leaving a visible gap between the
  description tail and the right-pinned elapsed when the terminal
  was wider than the content.

Layout C keeps the right column flex-shrink:0 so elapsed + tokens are
never clipped, but DROPS flex-grow on the left so the two columns sit
side-by-side: empty slack falls off the row tail (invisible) instead
of opening a gap inside the row. Identity (type) and intent
(description / activity) read first, cost reads last — matching the
natural visual hierarchy. When the row overflows the panel width the
left column truncates with `…` mid-row, while elapsed + tokens stay
intact.

Verified at 60 / 100 / 200 cols — at 200 cols there is no internal
gap and no trailing ellipsis; at 60 cols time + tokens stay visible
on the right and the description / activity tail truncates with `…`.

* fix(cli): port Claude Code's bullet + arrow visual to LiveAgentPanel rows

Adopt the leaked CoordinatorTaskPanel visual conventions:

- `○` replaces `⊷` for live (running) slots — matches Claude Code's
  use of `figures.circle` for the active-agent bullet, gives a
  uniform list look across the running roster. Terminal states
  keep distinct check / cross marks (✔ / ✖) so they're easy to
  scan at a glance.
- `▶` separates the description from elapsed / tokens, mirroring
  Claude's `PLAY_ICON` suffix marker.
- Activity is wrapped in `( ... )` so it reads as an annotation on
  the description rather than a sibling field, and the type prefix
  switches from ` · ` to `: ` (e.g. `editor: tighten import order`)
  to match Claude's `name: description` pattern.

The two-column flex layout from layout C is preserved — left column
flex-shrink:1 with truncate-end, right column (` ▶ Ns · Nk tokens`)
flex-shrink:0 so elapsed + tokens are never clipped, regardless of
how long the description / activity grows. This is the one
intentional divergence from Claude's literal pattern, which puts
elapsed at the row tail without pinning and lets it disappear off
narrow terminals.

Verified at 60 / 100 / 200 cols: at 200 cols the row is flush with
no internal gap; at 60 cols the description / activity tail
truncates with `…` while elapsed + tokens stay visible on the right.

* fix(cli): widen LiveAgentPanel, drop [in turn] marker, point overflow at dialog

Three usability fixes from review:

1. Use `terminalWidth` instead of `mainAreaWidth`. The latter is
   capped at 100 cols (intended for markdown / code where soft-wrap
   matters), which on a 200-col terminal left half the screen empty
   to the right of an already-truncating row. Live progress lines
   have nothing to soft-wrap, so the panel wants the full width.

2. Drop the `[in turn]` foreground marker. The flavor distinction
   matters in BackgroundTasksDialog (cancel semantics differ for
   foreground vs background entries) but in the glance panel the
   marker reads as cryptic noise — users asked what it meant. Keep
   the dialog as the surface that surfaces it.

3. Annotate the overflow callout with `(↓ to view all)`. The panel
   is intentionally read-only (it has no keyboard focus so it can't
   steal input from the composer), so when the roster outgrows the
   row budget we point users at the existing dialog — same keystroke
   the footer pill uses, kept in sync so users only learn one
   gesture.

* fix(cli): make Down on focused BackgroundTasksPill open the dialog

The focus chain Composer → AgentTabBar → BackgroundTasksPill is
walked with the Down arrow, but Down dead-ended at the pill — the
pill only opened the dialog on Enter. Users who followed the
LiveAgentPanel's "(↓ to view all)" overflow callout reached the
highlighted pill and got stuck there, defeating the hint.

Route Down on the focused pill into openDialog so the chain
completes naturally: Composer ↓ → AgentTabBar ↓ → Pill ↓ → Dialog.
Enter still works, so existing muscle memory keeps functioning.

* fix(cli): address Copilot review on LiveAgentPanel — interval gate, ghost rows, dead doc

Three findings from Copilot's PR review:

1. **1s interval kept ticking forever after expiry.** The gate was
   `entries.some(isAgentEntry)`, but `BackgroundTaskRegistry.getAll()`
   retains terminal entries indefinitely — once the last visible row
   passed its 8s window the panel returned null but the interval kept
   firing setNow each second, churning re-renders for nothing on
   screen. The gate now considers visibility (running / paused OR
   terminal-within-window) and the interval clears itself once the
   condition flips false. New entries restart the interval via the
   `entries` dep.

2. **Ghost rows when registry forgets an entry.** The live re-pull
   fell back to the snapshot when `registry.get()` returned
   undefined. The canonical case is a foreground subagent that
   unregisters silently after its statusChange fires
   (`unregisterForeground` deletes without emitting a follow-up
   transition) — the snapshot still says `running`, so the row
   would never clear. Trust the registry: when it says the entry
   is gone, drop the row. The snapshot-only fallback is preserved
   for the no-Config case (test fixtures).

3. **Dead doc reference.** The trailing comment in `subagents/index.ts`
   pointed at `docs/comparison/subagent-display-deep-dive.md`, which
   doesn't exist in this repo (it lives in the codeagents knowledge
   base, not qwen-code). Dropped the dead pointer; the in-tree
   pointers to `LiveAgentPanel` and `BackgroundTasksDialog` already
   tell readers where to look.

Coverage delta: +2 cases on `LiveAgentPanel.test.tsx` — `drops
snapshot rows the live registry no longer knows about` (issue #2)
and `still shows the snapshot when no Config is mounted` (locks in
the test-fixture fallback that issue #2's stricter rule would
otherwise have broken).

* fix(cli): address Copilot second-pass review — dialogOpen tick gate, isFocused doc

Two further findings from Copilot:

1. **Interval kept ticking while bg-tasks dialog was open.** The
   first-pass fix already torn the tick down once all rows expired
   from the visibility window, but `dialogOpen` was a separate
   reason the panel returned null and was missed by the gate. Add
   `dialogOpen` to the useEffect deps and short-circuit when true,
   so the dialog's tenure is interval-free.

2. **Stale `isFocused` doc comment in `ToolMessageProps`.** The
   comment claimed the prop controlled the now-retired `Ctrl+E /
   Ctrl+F` display shortcuts (those died with the inline
   `AgentExecutionDisplay` frame). Rewrite the comment to describe
   the only remaining behavior — the focus-routed approval surface
   (focus-holder banner vs. queued-sibling marker).

Coverage delta: +1 case on `LiveAgentPanel.test.tsx` — `tears the
1s tick down when the bg-tasks dialog opens` advances 60s of fake
time with `dialogOpen=true` and asserts no panel state drift.

* fix(cli): reconcile registry-missing snapshots as just-finished + address review nits

Hot-fix: the previous round's "drop the row when registry.get()
returns undefined" was too aggressive. `unregisterForeground` calls
`emitStatusChange(entry)` BEFORE it deletes the entry, so the
snapshot useBackgroundTaskView captures still says "running" while
the very next render's registry.get sees nothing. Dropping the row
outright made foreground subagents disappear from the panel the
instant they finished — users saw "SubAgents 不显示了" on tasks that
ran-and-immediately-completed.

Reconciliation now has three branches:
  1. live found → use live (newest recentActivities).
  2. snap says still-live but registry forgot → synthesize a
     terminal version with endTime pinned to the FIRST observation
     so the 8s visibility window gives the user a "the agent
     finished" beat then evicts cleanly. The first-seen-missing
     timestamp is held in a useRef map (without it, each tick
     resets endTime to `now` and the row never expires).
  3. snap is already terminal but registry forgot → drop (no
     useful state to keep showing).

Also addresses three smaller review notes (deepseek-v4-pro via
/review):

- DEFAULT_SUBAGENT_TYPE is now imported from
  @qwen-code/qwen-code-core (a new DEFAULT_BUILTIN_SUBAGENT_TYPE
  export referenced by both BuiltinAgentRegistry's seed entry and
  the panel's default-type elision). A backend rename now propagates
  instead of silently re-introducing the redundant
  `general-purpose:` prefix on every row.
- The useMemo body now reads `now` (as `reconcileAt`) so the
  dependency is semantically honest — a future "remove dead dep"
  cleanup can no longer silently freeze the panel on the first
  tool-call after a snapshot refresh.

Coverage delta: +5 cases on LiveAgentPanel.test.tsx — token rendering
on completed entries, status-icon routing for paused / failed /
cancelled (parametrized), case-insensitive prefix stripping in
descriptionWithoutPrefix, plus the rewritten ghost-row case
(synthesized terminal lingers 8s then evicts) and a sibling case
asserting already-terminal snapshots with empty registry still drop.

17 LiveAgentPanel tests pass.

* refactor(cli): split LiveAgentPanelBody + drop dead isPending/isWaitingForOtherApproval props

Two structural reviews from deepseek-v4-pro via /review:

1. **Hook-order footgun.** The `if (dialogOpen) return null` guard
   sat between the hook block and a substantial block of pure-render
   code; an extension that added `useMemo`/`useRef`/`useCallback`
   below the guard would crash with "Rendered fewer hooks than
   expected" the next time `dialogOpen` toggled. Extract the pure-
   render logic into `LiveAgentPanelBody` so the guard becomes the
   parent's last statement, and any future "add a hook to the body"
   refactor naturally lands in the inner component (hook-free
   today, hook-free for as long as it stays a presentational FC).

2. **Dead pass-through removed.** `isPending` and
   `isWaitingForOtherApproval` were dead in the subagent renderer
   (LiveAgentPanel + BackgroundTasksDialog own that surface) but
   ToolGroupMessage still computed `isWaitingForOtherApproval` and
   forwarded both into ToolMessage. Drop them from
   `ToolMessageProps`, drop the computation + forwarding in
   ToolGroupMessage, drop the test factory references. ToolGroupMessage
   keeps `isPending` on its own props for upstream caller compatibility
   (HistoryItemDisplay et al. forward it) but stops destructuring
   it; the doc comment now points at the surfaces that own that
   gating today.

Coverage: existing 115 cases across LiveAgentPanel /
BackgroundTasksDialog / BackgroundTasksPill / ToolMessage /
ToolGroupMessage all green; the panel split is purely structural
and the dead-prop removal was verified TS-clean before commit.

* fix(cli): map internal tool names to user-facing display names in LiveAgentPanel rows

`recentActivities[].name` carries the internal tool name from
AgentToolCallEvent (e.g. `run_shell_command`, `glob`). Rendering it
verbatim surfaced raw identifiers in the panel
(`run_shell_command rg TODO`) while BackgroundTasksDialog already
mapped through ToolDisplayNames to show user-facing names (`Shell rg
TODO`) — the two surfaces' vocabularies drifted on the same data.

Mirror the dialog's `TOOL_DISPLAY_BY_NAME` lookup so the panel and
dialog speak the same vocabulary. Added a test asserting
`run_shell_command` renders as `Shell` and the raw name is not
surfaced.

Coverage delta: +1 case on LiveAgentPanel.test.tsx (18 total).

* fix(cli): keep already-terminal snapshots visible until TTL + close 4 test gaps

1. **Terminal snap + missing registry now follows the visibility
   window.** Cancelled / failed foreground subagents go through
   `cancel`/`fail` (which stamp `endTime` and emit statusChange)
   followed by `unregisterForeground` (which deletes silently). The
   snap captures the real `endTime`, so the previous "drop on
   missing registry" branch made cancelled / failed foreground
   tasks disappear instantly — contradicting the panel's "brief
   terminal visibility" contract that the synthesized-completion
   path also relies on. Keep the snap as-is when `endTime` is set;
   the visibleAgents filter evicts it after `TERMINAL_VISIBLE_MS`
   like any other terminal entry. Defensive fallback drops the
   pathological "terminal status with no endTime" shape.
   (Copilot finding on PRRT_kwDOPB-92c6AS4z9.)

2. **Close 4 test gaps flagged by tanzhenxin's review:**
   - `elides the default 'general-purpose' subagent type from the row`
     locks in DEFAULT_BUILTIN_SUBAGENT_TYPE comparison.
   - `truncates the description tail when the panel width is too
     narrow` exercises the `width` prop (existing cases ignored it)
     and anchors on the right-pinned `▶ 3s` tail staying intact.
   - `clears the 1s tick interval when unmounted with live work in
     flight` spies on setInterval / clearInterval so a discarded
     fiber can't keep firing setNow.
   - `keeps terminal snapshots visible until the TTL even when the
     registry forgot them` covers the cancelled / failed foreground
     reconciliation path with a `✖` glyph + 9s eviction assertion.

Coverage: 22 LiveAgentPanel tests pass (was 18). All TS clean.

* refactor(cli): rename FOREGROUND_ROW_PREFIX from `[in turn]` to `[blocking]`

User feedback: `[in turn]` reads as "queued / sequential" — the
opposite of what it actually means (the row is blocking the user's
current turn). Even maintainers had to chase the source comment to
confirm the semantics, which is a strong signal that end users are
not getting the warning the prefix is supposed to deliver.

`[blocking]` reads more directly: "this row is what's holding up
your input", which is what cancelling it actually unblocks. Update
the in-row prefix and the cancel-confirmation hint in lockstep
(`x again to confirm stop · ends the blocking turn`) so the two
surfaces share vocabulary.

LiveAgentPanel still suppresses the marker in its row (the panel
has no cancel surface, so the warning has nothing actionable to
pair with); update the historical comment + reinforce the test
guard so neither the legacy `[in turn]` nor the new `[blocking]`
bleeds into the glance roster.

* fix(cli): address 4 review findings — height budget, neutral synthesis glyph, stale comments

1. **`availableTerminalHeight` regression** (claude-opus-4-7 via
   /qreview, DefaultAppLayout.tsx:127). LiveAgentPanel rendered
   OUTSIDE the `mainControlsRef` Box, so its 2-7 rows were not
   measured by `controlsHeight` and not subtracted from
   `availableTerminalHeight = terminalHeight - controlsHeight -
   staticExtraHeight - 2 - tabBarHeight` in AppContainer. Pending
   tool results in MainContent could render past the visible area
   and push the composer / panel off-screen — a regression vs PR
   #3768 which suppressed the inline frame in the live phase. Move
   the panel INSIDE the `mainControlsRef` Box so `measureElement`
   picks up its rows automatically; no new infrastructure needed.

2. **Neutral glyph for synthesized terminal rows** (claude-opus-4-7,
   LiveAgentPanel.tsx:278). The synthesis branch hardcoded
   `status: 'completed'` regardless of the actual outcome. Foreground
   subagents that errored or were cancelled were rendered with the
   green ✔ for 8s — directly contradicting the inline tool result
   the user just saw (`Subagent execution failed.` /
   `Agent was cancelled by the user.`). Add a `synthesized` flag
   on the synthesized rows; `statusIcon` checks it first and
   returns a neutral `·` + secondary color, so the panel never lies
   about an outcome it cannot determine. Status stays `'completed'`
   purely so the visibility-window filter treats the row as
   terminal.

3. **PR description claim retired** (Copilot, ToolMessage.tsx:411).
   The body's Reviewer Notes section claimed `isPending` /
   `isWaitingForOtherApproval` were kept on `ToolMessageProps` as
   vestigial pass-through; the props were actually removed in
   commit 93036b1d0. Will update the PR description in a follow-up
   non-code edit (the comment thread itself is on now-outdated
   line 411).

4. **Stale comment in ToolGroupMessage** (Copilot,
   ToolGroupMessage.tsx:303). The comment above the focus-routing
   logic referenced the retired Ctrl+E / Ctrl+F display shortcuts.
   Rewrite it to describe the current behavior — focus routing for
   inline approval prompts only — and call out where the live
   progress / drill-down moved (LiveAgentPanel + BackgroundTasksDialog).

Coverage delta: existing `reconciles snapshots…` test rewritten to
assert `·` instead of `✔` (and explicitly assert `not.toContain('✔')`);
new sibling `keeps the success glyph for entries the registry still
tracks (non-synthesized)` locks the non-synthesis path against a
future regression that always returns the neutral glyph. 68
background-view tests + 128 messages/layouts tests pass.

* fix(cli): ANSI-escape user-controlled strings + restore one-line scrollback summary

Two findings from deepseek-v4-pro via /review:

1. **ANSI sanitization on the panel** (LiveAgentPanel.tsx:368). The
   row was rendering `entry.subagentType`, `descriptionWithoutPrefix`
   output, and `recentActivities[].description` straight through Ink's
   `<Text>` — both subagent config (user-authored) and tool-call
   description (LLM-generated) can contain terminal control sequences
   that bleed through and corrupt the panel chrome. Apply
   `escapeAnsiCtrlCodes` to all three (HistoryItemDisplay does the
   same on its user-facing content for the same reason).

2. **One-line scrollback summary for terminal subagents**
   (ToolMessage.tsx:295). The previous round retired the verbose
   inline frame entirely and SubagentExecutionRenderer returned null
   for all non-approval states. The result: completed subagent
   results disappeared from scrollback the moment LiveAgentPanel's
   8s window expired. Reopening a session or dismissing the dialog
   left no record. Add a single-line `SubagentScrollbackSummary` for
   completed / failed / cancelled states — `<icon> <name>:
   <description> · N tools · Xs · Yk tokens` (plus terminateReason
   on non-success). One row per agent, no flicker risk; the verbose
   frame stays retired.

Coverage: +3 cases — `escapes ANSI control codes in user-controlled
strings` (LiveAgentPanel), `completed subagent → renders a one-line
scrollback summary` and `failed subagent → renders summary with
terminate reason` (ToolMessage). 24 + 34 panel/ToolMessage tests
pass; broader 2894-test cli/ui sweep clean.

Note: this restores some inline rendering that the original feature
choice ("inline AgentExecutionDisplay 完全移除") removed entirely.
The compromise — one line per terminal agent vs the old 15-row
frame — preserves history while keeping the flicker fix that
motivated the retirement.

* fix(cli): paused agents count as active in tally + sync reconciliation comment

Two findings from Copilot:

1. **Header tally now matches what the user sees.** The numerator
   was `runningCount` (status === 'running'), but the panel also
   renders paused entries as active rows (warning color, ⏸ glyph).
   With only paused agents present the header read "(0/1)" while
   the row was clearly visible — a confusing mismatch. Rename to
   `activeCount` and include paused in the count. New test
   `counts paused agents as active in the header tally` locks the
   tally + glyph in.

2. **Reconciliation block comment now describes the four real
   paths**, not the older three:
   1. live found → use live
   2. snap still-live + registry forgot → synthesize neutral terminal
   3. snap terminal + endTime present → keep snap, let TTL filter
      evict it (the cancelled / failed foreground path the previous
      round added)
   4. snap terminal + no endTime → drop (upstream invariant violation)

   Comment is now in lockstep with the implementation; the prior
   "drop terminal-with-empty-registry outright" wording was stale
   after the TTL-keep change.

Coverage: 25 LiveAgentPanel tests pass (was 24).
2026-05-07 21:38:18 +08:00
.github ci: add Qwen Code issue follow-up bot workflow (#3854) 2026-05-07 13:52:52 +08:00
.husky Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
.qwen feat(skills): add tmux-real-user-testing skill for readable TUI test logs (#3577) 2026-04-29 11:19:00 +08:00
.vscode Merge branch 'main' into feat/sandbox-config-improvements 2026-03-06 14:38:39 +08:00
docs feat(cli): expand TUI markdown rendering (#3680) 2026-05-07 16:24:13 +08:00
docs-site feat: update docs 2025-12-15 09:47:03 +08:00
eslint-rules pre-release commit 2025-07-22 23:26:01 +08:00
integration-tests test(sdk): align tool-control E2E with prior-read enforcement (#3898) 2026-05-07 19:35:51 +08:00
packages feat(cli): replace inline AgentExecutionDisplay with always-on LiveAgentPanel (#3909) 2026-05-07 21:38:18 +08:00
scripts feat(cli): customize banner area (logo, title, hide) (#3710) 2026-05-07 10:17:53 +08:00
.dockerignore fix(cli): skip stdin read for ACP mode 2026-03-27 11:47:01 +00:00
.editorconfig pre-release commit 2025-07-22 23:26:01 +08:00
.gitattributes pre-release commit 2025-07-22 23:26:01 +08:00
.gitignore Feat/openrouter auth (#3576) 2026-04-27 14:47:44 +08:00
.npmrc chore: remove google registry 2025-08-08 20:45:54 +08:00
.nvmrc chore: Expand node version test matrix (#2700) 2025-07-21 16:33:54 -07:00
.prettierignore Merge branch 'main' into feat/add-vscode-settings-json-schema 2026-03-03 11:21:57 +08:00
.prettierrc.json pre-release commit 2025-07-22 23:26:01 +08:00
.yamllint.yml Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
AGENTS.md feat(vscode): expose /skills as slash command with secondary picker (#2548) 2026-04-24 23:28:53 +08:00
CONTRIBUTING.md docs: add Screenshots/Video Demo section to PR template 2026-03-20 16:59:53 +08:00
Dockerfile refactor: Extract web-templates package and unify build/pack workflow 2026-02-26 21:02:46 +08:00
esbuild.config.js feat: add wasm build config (#2985) 2026-04-09 14:21:00 +08:00
eslint.config.js feat(cli): add API preconnect to reduce first-call latency (#3318) 2026-04-27 06:54:55 +08:00
LICENSE Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
Makefile feat: update docs 2025-12-22 21:11:33 +08:00
package-lock.json chore(release): v0.15.7 (#3907) 2026-05-07 18:50:01 +08:00
package.json chore(release): v0.15.7 (#3907) 2026-05-07 18:50:01 +08:00
README.md fix(cli): warn on ignored provider generation config (#3883) 2026-05-07 14:46:23 +08:00
SECURITY.md fix: update security vulnerability reporting channel 2026-02-24 14:22:47 +08:00
tsconfig.json # 🚀 Sync Gemini CLI v0.2.1 - Major Feature Update (#483) 2025-09-01 14:48:55 +08:00
vitest.config.ts test(channels): add comprehensive test suites for channel adapters 2026-03-27 15:26:39 +00:00

npm version License Node.js Version Downloads

QwenLM%2Fqwen-code | Trendshift

An open-source AI agent that lives in your terminal.

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

🎉 News

  • 2026-04-15: Qwen OAuth free tier has been discontinued. To continue using Qwen Code, switch to Alibaba Cloud Coding Plan, OpenRouter, Fireworks AI, or bring your own API key. Run qwen auth to configure.

  • 2026-04-13: Qwen OAuth free tier policy update: daily quota adjusted to 100 requests/day (from 1,000).

  • 2026-04-02: Qwen3.6-Plus is now live! Get an API key from Alibaba Cloud ModelStudio to access it through the OpenAI-compatible API.

  • 2026-02-16: Qwen3.5-Plus is now live!

Why Qwen Code?

Qwen Code is an open-source AI agent for the terminal, optimized for Qwen series models. It helps you understand large codebases, automate tedious work, and ship faster.

  • Multi-protocol, flexible providers: use OpenAI / Anthropic / Gemini-compatible APIs, Alibaba Cloud Coding Plan, OpenRouter, Fireworks AI, or bring your own API key.
  • Open-source, co-evolving: both the framework and the Qwen3-Coder model are open-source—and they ship and evolve together.
  • Agentic workflow, feature-rich: rich built-in tools (Skills, SubAgents) for a full agentic workflow and a Claude Code-like experience.
  • Terminal-first, IDE-friendly: built for developers who live in the command line, with optional integration for VS Code, Zed, and JetBrains IDEs.

Installation

Linux / macOS

bash -c "$(curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh)"

Windows (Run as Administrator)

Works in both Command Prompt and PowerShell:

powershell -Command "Invoke-WebRequest 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.bat' -OutFile (Join-Path $env:TEMP 'install-qwen.bat'); & (Join-Path $env:TEMP 'install-qwen.bat')"

Note

: It's recommended to restart your terminal after installation to ensure environment variables take effect.

Manual Installation

Prerequisites

Make sure you have Node.js 20 or later installed. Download it from nodejs.org.

NPM

npm install -g @qwen-code/qwen-code@latest

Homebrew (macOS, Linux)

brew install qwen-code

Quick Start

# Start Qwen Code (interactive)
qwen

# Then, in the session:
/help
/auth

On first use, you'll be prompted to sign in. You can run /auth anytime to switch authentication methods.

Example prompts:

What does this project do?
Explain the codebase structure.
Help me refactor this function.
Generate unit tests for this module.
Click to watch a demo video

🦞 Use Qwen Code for Coding Tasks in Claw

Copy the prompt below and paste it 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.

Authentication

Qwen Code supports the following authentication methods:

  • API Key (recommended): use an API key from Alibaba Cloud Model Studio (Beijing / intl) or any supported provider (OpenAI, Anthropic, Google GenAI, and other compatible endpoints).
  • Coding Plan: subscribe to the Alibaba Cloud Coding Plan (Beijing / intl) for a fixed monthly fee with higher quotas.

⚠️ Qwen OAuth was discontinued on April 15, 2026. If you were previously using Qwen OAuth, please switch to one of the methods above. Run qwen and then /auth to reconfigure.

Use an API key to connect to Alibaba Cloud Model Studio or any supported provider. Supports multiple protocols:

  • OpenAI-compatible: Alibaba Cloud ModelStudio, ModelScope, OpenAI, OpenRouter, and other OpenAI-compatible providers
  • Anthropic: Claude models
  • Google GenAI: Gemini models

The recommended way to configure models and providers is by editing ~/.qwen/settings.json (create it if it doesn't exist). This file lets you define all available models, API keys, and default settings in one place.

Quick Setup in 3 Steps

Step 1: Create or edit ~/.qwen/settings.json

Here is a complete example:

{
  "modelProviders": {
    "openai": [
      {
        "id": "qwen3.6-plus",
        "name": "qwen3.6-plus",
        "baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1",
        "description": "Qwen3-Coder via Dashscope",
        "envKey": "DASHSCOPE_API_KEY"
      }
    ]
  },
  "env": {
    "DASHSCOPE_API_KEY": "sk-xxxxxxxxxxxxx"
  },
  "security": {
    "auth": {
      "selectedType": "openai"
    }
  },
  "model": {
    "name": "qwen3.6-plus"
  }
}

Step 2: Understand each field

Field What it does
modelProviders Declares which models are available and how to connect to them. Keys like openai, anthropic, gemini represent the API protocol.
modelProviders[].id The model ID sent to the API (e.g. qwen3.6-plus, gpt-4o).
modelProviders[].envKey The name of the environment variable that holds your API key.
modelProviders[].baseUrl The API endpoint URL (required for non-default endpoints).
env A fallback place to store API keys (lowest priority; prefer .env files or export for sensitive keys).
security.auth.selectedType The protocol to use on startup (openai, anthropic, gemini, vertex-ai).
model.name The default model to use when Qwen Code starts.

Step 3: Start Qwen Code — your configuration takes effect automatically:

qwen

Use the /model command at any time to switch between all configured models.

More Examples
Coding Plan (Alibaba Cloud ModelStudio) — fixed monthly fee, higher quotas
{
  "modelProviders": {
    "openai": [
      {
        "id": "qwen3.6-plus",
        "name": "qwen3.6-plus (Coding Plan)",
        "baseUrl": "https://coding.dashscope.aliyuncs.com/v1",
        "description": "qwen3.6-plus from ModelStudio Coding Plan",
        "envKey": "BAILIAN_CODING_PLAN_API_KEY"
      },
      {
        "id": "qwen3.5-plus",
        "name": "qwen3.5-plus (Coding Plan)",
        "baseUrl": "https://coding.dashscope.aliyuncs.com/v1",
        "description": "qwen3.5-plus with thinking enabled from ModelStudio Coding Plan",
        "envKey": "BAILIAN_CODING_PLAN_API_KEY",
        "generationConfig": {
          "extra_body": {
            "enable_thinking": true
          }
        }
      },
      {
        "id": "glm-4.7",
        "name": "glm-4.7 (Coding Plan)",
        "baseUrl": "https://coding.dashscope.aliyuncs.com/v1",
        "description": "glm-4.7 with thinking enabled from ModelStudio Coding Plan",
        "envKey": "BAILIAN_CODING_PLAN_API_KEY",
        "generationConfig": {
          "extra_body": {
            "enable_thinking": true
          }
        }
      },
      {
        "id": "kimi-k2.5",
        "name": "kimi-k2.5 (Coding Plan)",
        "baseUrl": "https://coding.dashscope.aliyuncs.com/v1",
        "description": "kimi-k2.5 with thinking enabled from ModelStudio Coding Plan",
        "envKey": "BAILIAN_CODING_PLAN_API_KEY",
        "generationConfig": {
          "extra_body": {
            "enable_thinking": true
          }
        }
      }
    ]
  },
  "env": {
    "BAILIAN_CODING_PLAN_API_KEY": "sk-xxxxxxxxxxxxx"
  },
  "security": {
    "auth": {
      "selectedType": "openai"
    }
  },
  "model": {
    "name": "qwen3.6-plus"
  }
}

Subscribe to the Coding Plan and get your API key at Alibaba Cloud ModelStudio(Beijing) or Alibaba Cloud ModelStudio(intl).

Multiple providers (OpenAI + Anthropic + Gemini)
{
  "modelProviders": {
    "openai": [
      {
        "id": "gpt-4o",
        "name": "GPT-4o",
        "envKey": "OPENAI_API_KEY",
        "baseUrl": "https://api.openai.com/v1"
      }
    ],
    "anthropic": [
      {
        "id": "claude-sonnet-4-20250514",
        "name": "Claude Sonnet 4",
        "envKey": "ANTHROPIC_API_KEY"
      }
    ],
    "gemini": [
      {
        "id": "gemini-2.5-pro",
        "name": "Gemini 2.5 Pro",
        "envKey": "GEMINI_API_KEY"
      }
    ]
  },
  "env": {
    "OPENAI_API_KEY": "sk-xxxxxxxxxxxxx",
    "ANTHROPIC_API_KEY": "sk-ant-xxxxxxxxxxxxx",
    "GEMINI_API_KEY": "AIzaxxxxxxxxxxxxx"
  },
  "security": {
    "auth": {
      "selectedType": "openai"
    }
  },
  "model": {
    "name": "gpt-4o"
  }
}
Enable thinking mode (for supported models like qwen3.5-plus)
{
  "modelProviders": {
    "openai": [
      {
        "id": "qwen3.5-plus",
        "name": "qwen3.5-plus (thinking)",
        "envKey": "DASHSCOPE_API_KEY",
        "baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1",
        "generationConfig": {
          "extra_body": {
            "enable_thinking": true
          }
        }
      }
    ]
  },
  "env": {
    "DASHSCOPE_API_KEY": "sk-xxxxxxxxxxxxx"
  },
  "security": {
    "auth": {
      "selectedType": "openai"
    }
  },
  "model": {
    "name": "qwen3.5-plus"
  }
}

Tip: You can also set API keys via export in your shell or .env files, which take higher priority than settings.jsonenv. See the authentication guide for full details.

Security note: Never commit API keys to version control. The ~/.qwen/settings.json file is in your home directory and should stay private.

Local Model Setup (Ollama / vLLM)

You can also run models locally — no API key or cloud account needed. This is not an authentication method; instead, configure your local model endpoint in ~/.qwen/settings.json using the modelProviders field.

Set generationConfig.contextWindowSize inside the matching provider entry and adjust it to the context length configured on your local server.

Ollama setup
  1. Install Ollama from ollama.com
  2. Pull a model: ollama pull qwen3:32b
  3. Configure ~/.qwen/settings.json:
{
  "modelProviders": {
    "openai": [
      {
        "id": "qwen3:32b",
        "name": "Qwen3 32B (Ollama)",
        "baseUrl": "http://localhost:11434/v1",
        "description": "Qwen3 32B running locally via Ollama",
        "generationConfig": {
          "contextWindowSize": 131072
        }
      }
    ]
  },
  "security": {
    "auth": {
      "selectedType": "openai"
    }
  },
  "model": {
    "name": "qwen3:32b"
  }
}
vLLM setup
  1. Install vLLM: pip install vllm
  2. Start the server: vllm serve Qwen/Qwen3-32B
  3. Configure ~/.qwen/settings.json:
{
  "modelProviders": {
    "openai": [
      {
        "id": "Qwen/Qwen3-32B",
        "name": "Qwen3 32B (vLLM)",
        "baseUrl": "http://localhost:8000/v1",
        "description": "Qwen3 32B running locally via vLLM",
        "generationConfig": {
          "contextWindowSize": 131072
        }
      }
    ]
  },
  "security": {
    "auth": {
      "selectedType": "openai"
    }
  },
  "model": {
    "name": "Qwen/Qwen3-32B"
  }
}

Usage

As an open-source terminal agent, you can use Qwen Code in four primary ways:

  1. Interactive mode (terminal UI)
  2. Headless mode (scripts, CI)
  3. IDE integration (VS Code, Zed)
  4. SDKs (TypeScript, Python, Java)

Interactive mode

cd your-project/
qwen

Run qwen in your project folder to launch the interactive terminal UI. Use @ to reference local files (for example @src/main.ts).

Headless mode

cd your-project/
qwen -p "your question"

Use -p to run Qwen Code without the interactive UI—ideal for scripts, automation, and CI/CD. Learn more: Headless mode.

IDE integration

Use Qwen Code inside your editor (VS Code, Zed, and JetBrains IDEs):

SDKs

Build on top of Qwen Code with the available SDKs:

Python SDK example:

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())

Commands & Shortcuts

Session Commands

  • /help - Display available commands
  • /clear - Clear conversation history
  • /compress - Compress history to save tokens
  • /stats - Show current session information
  • /bug - Submit a bug report
  • /exit or /quit - Exit Qwen Code

Keyboard Shortcuts

  • Ctrl+C - Cancel current operation
  • Ctrl+D - Exit (on empty line)
  • Up/Down - Navigate command history

Learn more about Commands

Tip: In YOLO mode (--yolo), vision switching happens automatically without prompts when images are detected. Learn more about Approval Mode

Configuration

Qwen Code can be configured via settings.json, environment variables, and CLI flags.

File Scope Description
~/.qwen/settings.json User (global) Applies to all your Qwen Code sessions. Recommended for modelProviders and env.
.qwen/settings.json Project Applies only when running Qwen Code in this project. Overrides user settings.

The most commonly used top-level fields in settings.json:

Field Description
modelProviders Define available models per protocol (openai, anthropic, gemini, vertex-ai).
env Fallback environment variables (e.g. API keys). Lower priority than shell export and .env files.
security.auth.selectedType The protocol to use on startup (e.g. openai).
model.name The default model to use when Qwen Code starts.

See the Authentication section above for complete settings.json examples, and the settings reference for all available options.

Benchmark Results

Terminal-Bench Performance

Agent Model Accuracy
Qwen Code Qwen3-Coder-480A35 37.5%
Qwen Code Qwen3-Coder-30BA3B 31.3%

Ecosystem

Looking for a graphical interface?

  • 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

Troubleshooting

If you encounter issues, check the troubleshooting guide.

Common issues:

  • Qwen OAuth free tier was discontinued on 2026-04-15: Qwen OAuth is no longer available. Run qwen/auth and switch to API Key or Coding Plan. See the Authentication section above for setup instructions.

To report a bug from within the CLI, run /bug and include a short title and repro steps.

Connect with Us

Acknowledgments

This project is based on Google Gemini CLI. We acknowledge and appreciate the excellent work of the Gemini CLI team. Our main contribution focuses on parser-level adaptations to better support Qwen-Coder models.