Find a file
ChiGao c48dc32b57
fix(daemon): resolve ACP permission votes across connections (#5912)
* fix(daemon): resolve acp permission votes across connections

* fix(daemon): address review on acp permission vote resolution

Resolve the review feedback on PR #5912:

- dispatch.ts session/permission: add server-side stderr logging to every
  failure mode (missing requestId, no pending entry, ownership failure,
  bridge rejection) so a stuck permission prompt is debuggable, matching the
  legacy resolveClientResponse path.
- On bridge rejection (accepted === false), stop deleting the pending entry
  and stop reusing the "no pending" error. Keep the entry until teardown (as
  the legacy path does) and return a distinct 409 "vote not accepted" error,
  so the two states aren't conflated and a retry on another connection can
  still land.
- connection-registry.ts: extract findPendingPermissionEntry shared by
  findPendingPermission and deletePendingPermission so the matching predicate
  lives in one place; delete now stops at the first (globally unique) match.
- index.ts: the abandonPending callback logs-and-returns-false before the
  dispatcher is initialized instead of throwing through the teardown path,
  matching the detachClient callback's defensive posture.
- Tests: cover the previously-untested handler branches (missing requestId,
  invalid outcome shapes, cancelled outcome, bridge rejection + sessionId
  inference + entry retention) and assert the connection-qualified id format
  and the undefined-sessionId lookup branch.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): log dropped cross-connection vote + cover handler error branches

Address the second ci-bot review round on PR #5912:

- dispatch.ts resolveClientResponse: the cross-connection ownership guard
  dropped a vote silently. Add a writeStderrLine so a vote rejected on the
  legacy path leaves the same grep-friendly operator signal the
  session/permission handler already emits — otherwise the agent's prompt
  stays blocked until teardown with no log to correlate.
- transport.test.ts: add end-to-end coverage for two previously-untested
  handler branches — the no-pending 404 response (requestId misses the
  registry with no sessionId) and the unowned-session rejection (a
  connection voting on a session it does not own).

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* refactor(daemon): harden acp permission lookup and align registry api

Address the third ci-bot review round on PR #5912 (all non-blocking
suggestions):

- connection-registry.ts: collapse the redundant private
  findPendingPermissionEntry pass-through into the public
  findPendingPermission, and align deletePendingPermission to the same
  (requestId, sessionId) argument order so the two can never be called with
  swapped string args (a swap would silently match nothing and leak the
  entry until teardown, with no type error).
- dispatch.ts session/permission: look the pending entry up by the
  globally-unique requestId alone and treat the entry's own session as
  authoritative; when the client supplies a sessionId that does not match,
  reject with an explicit 409 instead of routing requireOwned and the bridge
  vote at the wrong session (which left the real entry to leak until
  teardown).
- Tests: add the sessionId-mismatch rejection case and update call sites for
  the new argument order.

Out of scope and deferred: making the dispatcher's registry a required
constructor parameter (and the dependent dropResolvedPermission cleanup) —
that changes the constructor contract beyond this fix.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): delete resolved permission by precise map key, fix comment

Address wenshao's review on PR #5912: findPendingPermission matches on
bridgeRequestId (a per-request randomUUID), not the connection-qualified
conn.pending map key. Under multi-client attach a permission_request reaches
every co-owning connection, each minting its own entry that shares the same
bridgeRequestId — so more than one entry can match and the prior "globally
unique, at most one match" comment was wrong.

- connection-registry.ts: correct the findPendingPermission doc to attribute
  uniqueness to the map key (not matched here) and note co-owning connections
  can share a bridgeRequestId, so callers needing a specific entry must act on
  the conn/map-key they already hold.
- dispatch.ts dropResolvedPermission: delete the resolved entry by its exact
  conn/map-key instead of re-matching by bridgeRequestId, which under
  multi-attach could delete a sibling connection's entry and orphan the one
  just resolved. Drops the now-unused req parameter.

deletePendingPermission stays for the session/permission handler, where the
lookup and delete consistently target the same first match.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): align acp permission errors with REST 404/400/403 shapes

Address wenshao's two [Critical] review findings on PR #5912:

- session/permission no longer falls through to the bridge when the registry
  misses. In the scoped route a sessionId is always supplied, so a stale/
  unknown requestId previously routed to the caller's session, got a bridge
  `false`, and was reported as a thrown 409 — diverging from the established
  `404 -> false` contract of DaemonClient.respondToSessionPermission() and the
  REST route. Now a registry miss returns 404; 409 is reserved for a present
  entry the bridge still rejects.
- Wrap the bridge vote and map permission-specific throws like REST's
  sendPermissionVoteError: InvalidPermissionOptionError -> INVALID_PARAMS with
  httpStatus 400 + invalid_option_id, PermissionForbiddenError -> httpStatus
  403 + permission_forbidden (with requestId/sessionId/reason). Previously
  these fell through the outer catch into a generic httpStatus-less internal
  error, so SDK callers saw 500s for normal permission outcomes. Import the
  error classes from acp-session-bridge (as REST does) so instanceof matches
  the class the bridge throws.
- Tests: cover the 404-on-miss-with-sessionId regression and the 400/403
  mappings.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* refactor(daemon): unify permission delete, whitelist vote forward

Address the fourth ci-bot review round on PR #5912 (non-blocking
suggestions):

- session/permission success path now drops the resolved entry through the
  shared dropResolvedPermission helper using the conn/map-key pendingRef
  already carries, instead of re-matching by requestId. Unifies the two
  delete sites and keeps the deletion precise.
- parsePermissionResponse forwards only the bridge-contract fields (outcome
  plus the ACP-reserved _meta passthrough) rather than copying every
  remaining client key, removing a needless client-controlled surface on the
  server-side bridge argument.
- transport.test.ts: the cross-connection permission test now asserts a
  duplicate vote on the same id does not reach the bridge again, locking down
  the cleanup guarantee that is the core of this PR.

Declined (replied on the threads): a blanket local try/catch around the
bridge vote (would shadow the outer dispatcher's typed-error mapping for
non-permission errors) and success-side audit logging in the generic
findPendingClientRequest (log noise / out of scope).

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): full REST parity for acp permission vote errors

Address the fifth ci-bot review round on PR #5912:

- session/permission now maps every permission-specific bridge throw like
  REST's sendPermissionVoteError: InvalidPermissionOptionError -> 400,
  PermissionForbiddenError -> 403, PermissionPolicyNotImplementedError -> 501
  (policy), CancelSentinelCollisionError -> 500 (requestId/sentinel). The last
  two previously fell through to the outer dispatcher catch and became a
  generic -32603 without structured metadata.
- Truly unexpected bridge/sessionCtx failures now run the same
  cancelAbandonedPermission fallback as the legacy resolveClientResponse path
  (dropping the entry only if the cancel landed, else keeping it for teardown)
  before rethrowing — so an unexpected error no longer leaves the mediator
  blocking the agent's prompt until session teardown.
- parsePermissionResponse rebuilds the outcome sub-object from its validated
  keys instead of forwarding it verbatim, so a client can no longer inject
  extra outcome sub-fields (e.g. force) into the bridge argument; _meta is
  forwarded only when it is an object.
- Tests: cross-connection vote via the session/permission method (ack on the
  voter's stream + entry removed from the originator), plus the 501 and 500
  error mappings.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): preserve answers payload and delete only the voter's own entry

Address wenshao's two [Critical] findings on PR #5912:

- parsePermissionResponse no longer drops the AskUserQuestion `answers`
  payload. The whitelist tightening forwarded only outcome/_meta, but the
  bridge treats `answers` (an object map of string values) as the one
  supported non-ACP permission-response field, so votes were resolving while
  the agent received no submitted answers. Forward it under the same shape the
  bridge validates.
- The session/permission success path now deletes only the voting
  connection's OWN pending entry for the requestId, not the first
  registry-wide match. pendingRef can belong to a sibling connection; under
  the consensus policy respondToSessionPermission returns true for an
  intermediate "recorded" vote, so deleting a sibling's entry could drop a
  co-owner's still-needed request and stall the quorum. A cross-connection
  voter with no own entry deletes nothing and leaves the originator's entry
  for teardown.
- Tests: forward-answers/strip-unknown-fields case, and the cross-connection
  method test now asserts a co-owner's vote does NOT delete the originator's
  sibling entry.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): validate legacy vote path and scope permission deletes to voter

Address the sixth ci-bot review round on PR #5912 (2 Critical + 4 suggestions):

- resolveClientResponse now validates/whitelists the client result through the
  same parsePermissionResponse the session/permission handler uses. This PR
  had widened that legacy path to any co-owning connection (via
  findPendingClientRequest), so the raw `result as unknown` cast was a
  cross-connection injection surface for arbitrary top-level args and extra
  outcome sub-fields; a malformed result still throws and hits the cancel
  fallback as before.
- The unexpected-error cancel fallback in the session/permission handler now
  drops only the VOTING connection's own entry (via the new shared
  dropOwnPendingPermission helper), not pendingRef — which is the first
  registry-wide match and may be the originator's entry, whose deletion would
  stall a consensus quorum still awaiting other co-owners.
- parsePermissionResponse logs a stderr line when a present-but-malformed
  `answers` is dropped, instead of silently discarding it.
- Removed ConnectionRegistry.deletePendingPermission: it had no production
  callers and its first-match semantics were unsafe under co-owned sessions
  (deletion is done connection-scoped in the dispatcher).
- Tests: _meta object-preserved / non-object-dropped, and the generic
  unexpected-error fallthrough (cancel fallback runs + error propagates).

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): structured 403 for unowned session/permission vote

Address wenshao's remaining [Critical] on PR #5912: the session/permission
ownership rejection went through the shared requireOwned, which sends an
INVALID_PARAMS error with no `data` envelope. Every other error path in this
handler carries `{ httpStatus }` (404/409/400/403/500/501), so SDK callers
that classify permission-vote failures by error.data.httpStatus got undefined
for the likeliest cross-connection failure (right session header, no
session/new on this connection). Inline the ownership check so the rejection
carries httpStatus 403 + sessionId + requestId, leaving the shared requireOwned
untouched for other handlers. Test asserts the 403.

(wenshao's other two criticals — legacy raw-result forwarding and the
catch-all deleting the originator's entry — were already fixed in 4bb06e5fd.)

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): structured 400 for invalid session/permission params

Address the latest ci-bot suggestion on PR #5912: parsePermissionResponse
throws AcpParamError, a plain Error with no httpStatus, which the outer
dispatcher catch maps to a bare INVALID_PARAMS — inconsistent with every other
error path in this handler (404/409/400/403/500/501 all carry httpStatus).
Catch AcpParamError locally and return a structured 400 with requestId, so SDK
callers that classify by error.data.httpStatus see a consistent shape. The
parametrized invalid-outcome test now asserts the 400.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* perf(daemon): O(1) pending lookup, log vote success, cover malformed answers

Address wenshao's three suggestions on PR #5912:

- findPendingClientRequest parses the originating connectionId from the
  server-minted id format (_qwen_perm_<connectionId>_<counter>) for an O(1)
  byId lookup, falling back to the full scan for client-chosen ids.
- The session/permission success path now writes a stderr line ("vote
  accepted") so an operator debugging a stuck prompt can tell it apart from
  "vote never arrived" or "landed on another connection" — every failure
  branch already logs.
- Added a test for the malformed-answers branch (non-string values) asserting
  the vote still lands but answers are not forwarded to the bridge.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): httpStatus on missing-requestId + cross-conn malformed vote test

Address the latest ci-bot review on PR #5912:

- The missing-`requestId` rejection in session/permission was the only error
  branch without an { httpStatus } envelope. Add httpStatus 400 (+ requestId)
  so SDK callers can classify it like every other validation error here. Test
  asserts the 400.
- Add an integration test for the legacy resolveClientResponse cross-connection
  variant: connection B (a co-owner) answers connection A's permission request
  with a malformed result, parsePermissionResponse (added for this path) throws,
  and the cancel fallback still releases the mediator.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-30 22:43:52 +00:00
.github ci(workflows): remind authors not to force-push active PRs (#6035) 2026-06-30 14:55:00 +00:00
.husky Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
.qwen feat(daemon): support @extension mentions (#6008) 2026-06-29 16:30:47 +00:00
.vscode Merge branch 'main' into feat/sandbox-config-improvements 2026-03-06 14:38:39 +08:00
docs feat(web-shell): add mobile sidebar drawer with session list (#6003) 2026-06-30 15:34:10 +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 test(ci): stabilize cron interactive release check (#6016) 2026-06-29 17:07:50 +00:00
OpenClaw-Query-Submit@be66572805 feat(core): support glob patterns in mcp.allowed and mcp.excluded (#6012) 2026-06-30 06:16:47 +00:00
packages fix(daemon): resolve ACP permission votes across connections (#5912) 2026-06-30 22:43:52 +00:00
patches feat(browser-ext): revive Chrome extension via daemon-direct architecture (#5777) 2026-06-28 15:57:31 +00:00
scripts ci(workflows): remind authors not to force-push active PRs (#6035) 2026-06-30 14:55:00 +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(web-shell): add mobile sidebar drawer with session list (#6003) 2026-06-30 15:34:10 +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(browser-ext): revive Chrome extension via daemon-direct architecture (#5777) 2026-06-28 15:57:31 +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 feat(browser-ext): revive Chrome extension via daemon-direct architecture (#5777) 2026-06-28 15:57:31 +00:00
package.json fix(cli): Handle ACP read_file for managed local paths (#6021) 2026-06-30 11:15:49 +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.