* 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> |
||
|---|---|---|
| .github | ||
| .husky | ||
| .qwen | ||
| .vscode | ||
| docs | ||
| docs-site | ||
| eslint-rules | ||
| integration-tests | ||
| OpenClaw-Query-Submit@be66572805 | ||
| packages | ||
| patches | ||
| scripts | ||
| .dockerignore | ||
| .editorconfig | ||
| .gitattributes | ||
| .gitignore | ||
| .npmrc | ||
| .nvmrc | ||
| .prettierignore | ||
| .prettierrc.json | ||
| .yamllint.yml | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| CONTRIBUTING.md | ||
| Dockerfile | ||
| esbuild.config.js | ||
| eslint.config.js | ||
| eslint.legacy-filenames.mjs | ||
| LICENSE | ||
| Makefile | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| SECURITY.md | ||
| tsconfig.json | ||
| vitest.config.ts | ||
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.
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.
