Find a file
易良 de706203c0
feat(core,cli,sdk): resume an interrupted turn without a synthetic "continue" message (#5030)
* feat(core): add continueInterruptedTurn to resume unfinished turns from history

Detect how a session's last turn ended from persisted chat history alone
(no in-memory request refs, so it works across process restarts) and
continue it without injecting any synthetic user message:

- interrupted_prompt (orphaned trailing user entry): re-submit the same
  content via SendMessageType.Retry — strip + re-push, same logical turn.
- interrupted_turn (dangling functionCall): close each pair with a
  synthesized error functionResponse submitted as a ToolResult, the same
  continuation signal a real tool completion would have sent.
- none: idempotent no-op (null stream).

Core layer for the SDK/ACP "continue previous turn" capability discussed
in #4679; protocol exposure (_qwen/session/continue) comes separately.

* feat(cli,sdk): expose continue-interrupted-turn through ACP and SDK control protocol

ACP surface:
- New vendor ext method `qwen/session/continue` (acpAgent) backed by
  Session.continueTurn(): classifies the last turn from history and
  re-drives the model loop — Retry semantics for an orphaned trailing
  user entry, synthesized error tool results for dangling tool calls.
  No new user message enters the transcript; the prompt id reuses the
  interrupted turn's number. 409 when a turn is already in flight.
- The model-call ↔ tool-call loop is extracted verbatim from
  #executePrompt into #runModelTurnLoop (+ #finishTurn epilogue) and
  shared by both paths.
- initialize advertises the capability via agentCapabilities._meta
  (qwen.sessionContinue) per ACP extensibility guidance.

SDK surface (stream-json control protocol):
- New control request subtype `continue_last_turn` routed to
  SystemController and delegated to the nonInteractive Session, which
  schedules a continuation turn ahead of queued user messages.
- runNonInteractive gains `continueInterrupted` mode: detection +
  Retry/ToolResult first-turn override; reminder injection is skipped
  so tool_result parts stay at the head of the user message.
- Query.continueLastTurn() sends the request; reply reports
  { accepted, interruption } and resumed output flows as regular
  stream messages.

Part of the #4679 continue-previous-turn work; core detection landed
in the previous commit.

* fix(core): replay the orphaned user entry verbatim, reminders included

The Retry send path does not re-inject per-turn system reminders, so
filtering them out of the captured entry made the resumed request differ
from the original (the date reminder vanished). A continuation is the
same turn, not a fresh one — capture the trailing user entry byte-for-
byte so the resumed wire request is identical to the interrupted one.
Verified end-to-end against a request-capturing mock server.

* fix(cli): re-trigger work loop for pending continue turns; cover continueInterrupted

- ensureProcessingStarted's finally-block re-trigger condition omitted
  pendingContinueTurns, so a continue request arriving as the work loop
  wound down could be stranded until the next user message. Add it to
  the guard alongside the other queues.
- Add runNonInteractive({continueInterrupted}) unit tests: Retry path,
  ToolResult (interrupted_turn) path, and the clean-turn no-op.

* fix(core,cli): restore orphaned entries when a continued send is skipped

When resuming an interrupted turn, the orphaned trailing user entries are
stripped from history before the content is re-sent. If that send is then
skipped (e.g. session token limit exceeded), the stripped content was lost
from history entirely. Have strip return the removed entries and restore
them on any non-cancelled skip across the core retry, non-interactive, and
ACP continue paths.

On the ACP path the turn loop already re-adds functionResponse parts of an
unsent message via #preserveUnsentMessageHistory, so a tool_result orphan
would be written twice (once by preserve, once by restore). Let the caller
own restoring the initial message and skip the loop's functionResponse-only
preservation for that one message when it isn't a cancellation.

* fix(cli,sdk): address continuation review feedback

* fix(cli): address continuation review feedback

* fix(cli): preserve continuation constraints and drain state

* Merge latest main into feature branch

* fix(core,cli): harden continuation recovery

* fix(cli): close continuation review gaps

* chore(cli): restore acp bridge path mapping

* fix(cli): reject continuation during background turns

* refactor(cli): simplify continuation handling

* fix(cli): harden interrupted turn continuation

* refactor(cli): share continuation helper logic

* fix: harden interrupted-turn continuation

* chore: remove unrelated continuation diff churn

* refactor(cli): drop ACP session-continue surface from PR #5030

Issue #4679 only needs the SDK/stream-json continuation path. The SDK's
Query.continueLastTurn() rides the stdio control protocol handled by the
nonInteractive session, not ACP, and the qwen/session/continue vendor
method had no SDK caller. Revert the ACP continuation surface
(Session.continueTurn, #executeContinue, the qwen/session/continue routing
and capability advertisement, and their tests) to the merge-base so the PR
ships only the path the issue requires.

* refactor(core): rename turnInterruption to kebab-case turn-interruption

check-file/filename-naming-convention requires kebab-case for new files
(AGENTS.md), so turnInterruption.ts failed CI lint. Rename the module and
its test and update the index re-export.

* fix(core,cli): delegate interrupted-prompt continuation to the Retry path

The continuation path duplicated the orphan strip/restore that the Retry
send in client.ts already performs, and its restore guard flipped on
pre-push stream events, so an abort before the history push could
permanently drop the orphaned user entry. Delegate to the Retry
strip/restore, whose guard flips only after the content is committed.

Removing the local strip drops the now-dead maxEntries parameter from
stripOrphanedUserEntriesFromHistory, and detection now reads the full
history (getHistory) so its re-submitted parts match the unbounded strip.
Also fire onResultEmitted only after a successful emit, so a throwing
emitResult cannot lose both the result and the error.

* test(cli): cover insertAfterFunctionResponses ordering

Add direct unit coverage for the splice helper used by the continuation
reminder insertion: mixed parts, all-functionResponse input, empty
additions, and empty parts.

* feat(cli,acp,sdk): expose continue-last-turn to daemon + ACP and fix continuation review findings

Surface the interrupted-turn continuation through two more entry points, both
routing into a new Session.continueLastTurn() that reuses the core
detectTurnInterruption classification and the existing send/tool loop:

- daemon: new control method qwen/control/session/continue
  (status -> bridgeTypes -> bridge -> POST /session/:id/continue -> acpAgent
  handler), returning { accepted, interruption }.
- ACP: a `qwen.daemon.continueLastTurn` prompt _meta flag, mirroring the
  existing daemon retry meta. interrupted_prompt re-submits the stripped
  orphaned run; interrupted_turn closes dangling tool calls with synthesized
  error functionResponses. System reminders are now inserted after any leading
  functionResponse parts so a tool-result continuation keeps tool_result
  blocks first (normal prompts unchanged).

Also resolve the open continuation review findings:

- core/client: history-length snapshot guard so a Retry send that fails before
  the first event never duplicates history (replaces the retrySendStarted flag
  that missed pre-event failures).
- cli/nonInteractive/session: emit a terminal error result when an accepted
  continuation is abandoned by shutdown; emit a continue_turn_failed diagnostic
  when a continuation fails after a result was already flushed.
- sdk/query: idempotent finishTransportRead with optional error, also invoked
  on the message-router error path, so a transport crash rejects pending
  control (continueLastTurn) and MCP requests instead of hanging.

Adds regression tests across core/cli/sdk for each fix and the new surfaces.

* test(sdk): cover finishTransportRead MCP-response rejection on teardown

Addresses the review ask to validate that finishTransportRead rejects pending
MCP responses when the transport read loop ends. White-box: injects a pending
entry rather than standing up a full SDK MCP server handshake, since the
rejection drain is map-internal regardless of how the entry was registered.

* fix(acp): harden continueLastTurn — restore stripped orphan on send failure, fix ordering and error surfacing

Review follow-ups on the ACP/daemon continuation path:

- interrupted_prompt: preserve the full stripped orphan message back into
  history when a continuation send fails non-cancelled. Previously only
  functionResponse parts were kept, so a stripped text orphan was lost — the
  user's unanswered turn vanished from history.
- worktree restore notice: insert after any leading functionResponse parts so a
  tool-result continuation keeps tool_result blocks first (Anthropic ordering),
  matching the system-reminder insertion.
- continueLastTurn fire-and-forget: emit a client diagnostic on failure instead
  of log-only, so a failed continuation is visible rather than silently stalled.
- log at warn when the in-prompt re-detection finds no interrupted turn.

Adds a Session regression test asserting the orphan is preserved on a
non-cancelled continuation send failure.

* perf(acp,core): classify continuation from a bounded tail; O(1) retry length read

Review follow-ups (all non-blocking):

- continueLastTurn classifies from a shallow bounded tail
  (getHistoryTailShallow) instead of structuredClone-ing the entire history.
  The authoritative re-detection inside the fired prompt() still reads full
  history for the strip, matching the nonInteractive two-tier pattern.
- the Retry history-length guard reads getHistoryLength() (O(1)) instead of
  getHistory().length, which structuredClones the whole history just to read
  .length (the truncateHistory path already warns about this).

Adds a Session test for the pendingPrompt re-entrancy guard (continueLastTurn
rejects while a prompt is in flight).

* fix(acp-bridge): strip daemon continue meta key from external prompts

An external POST /session/:id/prompt could set
_meta['qwen.daemon.continueLastTurn'] and trigger a continuation through the
prompt FIFO, bypassing continueLastTurn()'s admission guards. sendPrompt now
strips it alongside the retry meta key; the legitimate path is the agent's
internal Session.prompt() and does not go through sendPrompt.

* fix(cli): resolve continuation review comments

- Session.ts: reuse the shared insertAfterFunctionResponses helper for both
  the reminder and worktree-notice phases (removes the duplicated findIndex
  pattern) and guard against duplicate reminders on an interrupted_prompt
  continuation, mirroring nonInteractiveCli.ts. Adds an ordering test.
- systemController.ts: point the missing-callback error at the wiring
  (onContinueLastTurn on ControlContext) instead of 'not available'.
- nonInteractive/session.ts: tag the continue logs with sessionId.

* fix(core): gate retry restore on a push counter, not history length

The Retry strip/restore guard compared history length to a post-strip
baseline to decide whether the re-submitted user content landed. Auto-
compression inside GeminiChat.sendMessageStream runs before the push and
shrinks history independently of it, so the length could fall below the
baseline even after a successful push, restoring the stripped entries and
duplicating the user's prompt (reachable on success once compaction removes
>=2 entries, common near the token limit).

GeminiChat now exposes a monotonic userContentPushCount (incremented when the
user content is pushed, decremented only if that push is rolled back on a
setup error). client.ts snapshots it after the strip and restores only when it
did not advance — invariant under compression.

* fix(sdk): log pending requests when the transport finalizes

finishTransportRead rejected pending control/MCP requests with no anchor log,
so a CLI crash mid-continuation left only scattered rejections. Emit one
correlatable line with the pending counts and error when work was in flight.

* fix(serve): track daemon continuation through the prompt-admission path

POST /session/:id/continue ran the continuation as an untracked internal
agent prompt (continueLastTurn fired Session.prompt() fire-and-forget via the
sessionContinue control method). The bridge never set pendingPromptCount /
promptActive / activePromptOriginatorClientId for it, so the daemon reported
the session idle during a continuation, a racing POST /prompt was admitted with
no FIFO wait and aborted the in-flight continuation, and the continuation's
events went out unattributed.

Now continueLastTurn is the accept/reject pre-check only; when it accepts,
bridge.continueSession drives the actual turn through sendPrompt with a trusted
continue flag (re-arming the continue meta key that sendPrompt strips from
untrusted callers), so the continuation gets full admission tracking, FIFO
serialization, originator attribution, and turn-complete broadcasting. The
/continue route forwards X-Qwen-Client-Id and continueSession validates it
up-front, matching POST /session/:id/prompt. The {accepted, interruption}
response contract is preserved.

Addresses the continuation-lifecycle review finding.

* test(serve): cover continuation admission tracking end-to-end

- bridge: assert a continuation reports the session BUSY (hasActivePrompt +
  pendingPromptCount) and that a racing prompt queues behind it via the FIFO
  instead of aborting it — the two behaviors the lifecycle fix targets.
- serve: assert POST /session/:id/continue forwards X-Qwen-Client-Id to
  continueSession so the continuation turn is attributed like a prompt.

* test(serve): add real-daemon smoke for POST /session/:id/continue

Exercises the full route → bridge → agent continuation path against a spawned
daemon: a fresh session has no interrupted turn, so /continue returns
{accepted:false, interruption:'none'} (model-free, like the prompt-admission
checks). Also fixes the now-stale DAEMON_CONTINUE_META_KEY comment to reflect
that continueSession (not continueLastTurn) re-arms the meta via sendPrompt.

* refactor(acp-bridge): drop dead AbortController in continueSession; harden reject test

- continueSession passes undefined instead of a never-aborted AbortController to
  sendPrompt (continuations cancel via the cancelSession route, not this handle).
- The 'rejected continueSession does not dispatch' test waits long enough for a
  regression fire-and-forget to reach the agent before asserting none ran,
  instead of a single microtask flush.

* fix(cli): restore orphaned turn when a continuation send throws

The interrupted_prompt continuation strips the orphaned user run from history
before re-sending it. #preserveUnsentMessageHistory only restores on a graceful
null-stream result, not when the send throws (e.g. a hard-rescue compression
error before the user-content push) — so the orphan was permanently lost
(transcript gap; --resume loads history missing the user's turn).

Capture the stripped entries + a GeminiChat push-count snapshot, and restore
them in the send-loop catch when the count did not advance (mirrors the core
Retry restore in client.ts) — so a later tool-loop failure, where the orphan
already landed, does not double-restore. Also emit conversation_finished on the
no-op continuation early-return, which sits before the send-loop try/finally.

* fix(serve): surface continuation admission failures + mirror prompt replay contract

continueSession previously swallowed post-accept admission failures (queue-full,
pre-aborted, session reaped) yet returned accepted:true, leaving SDK callers
waiting for output that never arrives. Now it dispatches sendPrompt
synchronously so an admission throw propagates (the caller gets an error, not a
false accept); only post-admission turn failures are logged (now via
teeServeDebugLine, so they reach the SSE diagnostic stream).

It also mirrors POST /session/:id/prompt's replay/correlation contract: capture
lastEventId + accept a route-generated promptId and return both on accept, so a
client attaching the SSE stream can replay missed events and correlate
turn_complete/turn_error. Adds tests for the combined retry+continue meta strip,
nonexistent-session and async-failure error paths, and the new contract fields.

* chore(core): log retry-restore decision for diagnostics

restoreStrippedRetryEntries mutates history in a finally with no trace; a single
debugLogger.info on the restore branch makes a future push-counter regression
(silent duplicate/loss) diagnosable.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-28 13:24:06 +00:00
.github ci(review): increase PR review timeout from 90 to 120 minutes (#5959) 2026-06-28 12:53:48 +00:00
.husky Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
.qwen refactor(cli): split serve server assembly (#5937) 2026-06-27 12:18:19 +00:00
.vscode Merge branch 'main' into feat/sandbox-config-improvements 2026-03-06 14:38:39 +08:00
docs refactor(cli): Remove serve bridge re-export shims (#5955) 2026-06-28 13:20:44 +00:00
docs-site Hide internal docs from docs site (#4357) 2026-06-01 15:55:14 +08:00
eslint-rules pre-release commit 2025-07-22 23:26:01 +08:00
integration-tests feat(core,cli,sdk): resume an interrupted turn without a synthetic "continue" message (#5030) 2026-06-28 13:24:06 +00:00
packages feat(core,cli,sdk): resume an interrupted turn without a synthetic "continue" message (#5030) 2026-06-28 13:24:06 +00:00
patches fix(input): restore IME cursor positioning reverted in #4779 (#4993) 2026-06-19 08:00:26 +08:00
scripts perf(cli): enable compile cache and defer getCliVersion for serve (#5938) 2026-06-27 14:19:29 +00: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 feat(installer): add standalone hosted install and uninstall flow (#3828) 2026-05-21 11:57:10 +08:00
.gitignore feat(memory): add a git-shared team memory tier (#5886) 2026-06-27 12:29:48 +00:00
.npmrc chore: remove google registry 2025-08-08 20:45:54 +08:00
.nvmrc chore(deps): upgrade ink 6.2.3 → 7.0.2 + bump Node engine to 22 (#3860) 2026-05-11 17:29:50 +08:00
.prettierignore feat(acp): support /cd command in ACP sessions (#5903) 2026-06-27 14:47:40 +00:00
.prettierrc.json pre-release commit 2025-07-22 23:26:01 +08:00
.yamllint.yml feat(desktop): Add desktop app package with Qwen ACP SDK integration (#3778) 2026-06-11 21:57:20 +08:00
AGENTS.md feat(lint): enforce kebab-case filenames with ESLint (#4797) 2026-06-22 10:10:07 +08:00
CHANGELOG.md chore(release): v0.19.3 (#5952) 2026-06-28 06:59:17 +00:00
CLAUDE.md docs: rewrite CLAUDE.md to point to AGENTS.md as authoritative source (#5138) 2026-06-15 15:23:26 +08:00
CONTRIBUTING.md docs: add provider preset governance policy to CONTRIBUTING.md (#5631) 2026-06-27 14:44:21 +00:00
Dockerfile chore(deps): upgrade ink 6.2.3 → 7.0.2 + bump Node engine to 22 (#3860) 2026-05-11 17:29:50 +08:00
esbuild.config.js feat(voice): voice dictation with native capture, streaming, and biasing (#5502) 2026-06-22 14:33:36 +08:00
eslint.config.js feat(cua-driver): vendor qwen-cua-driver with opt-in 0–1000 relative coordinates (#5896) 2026-06-26 13:06:43 +00:00
eslint.legacy-filenames.mjs refactor(cli): Finish serve kebab-case filenames (#5604) 2026-06-22 21:15:40 +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.19.3 (#5952) 2026-06-28 06:59:17 +00:00
package.json chore(release): v0.19.3 (#5952) 2026-06-28 06:59:17 +00:00
README.md docs: Revamp README for clarity and focus (#5257) 2026-06-18 10:27:16 +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 feat(channel): add QQ Bot (QQ机器人) channel adapter (#5202) 2026-06-19 06:32:52 +08:00

npm version License Node.js Version Downloads

QwenLM%2Fqwen-code | Trendshift

The open-source AI coding agent that lives in your terminal.

中文 | 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, daemon mode, SDKs, and IM bots (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

qwen          # Launch interactive terminal UI
# Inside the session:
/auth         # Configure your provider and API key

See the Authentication Guide and Settings Reference for detailed setup.

Qwen Code

How to Use Qwen Code

Mode Command Use Case
Interactive qwen Terminal UI with rich rendering, @file references, slash commands
Headless qwen -p "..." Scripts, CI/CD, batch processing — no UI
IDE VS Code, Zed, JetBrains
Desktop Qwen Code Desktop — GUI for macOS, Windows, Linux
Daemon qwen serve Shared agent session over HTTP+SSE (ACP). Multiple clients, one agent. (experimental) Docs
SDK TypeScript, Python, Java
IM Bot qwen channel Connect to Telegram, DingTalk, WeChat, or Feishu
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)

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.

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.