fix(cli): show ⌥T instead of alt+T on macOS for thinking expansion#3
Closed
Alex-ai-future wants to merge 549 commits into
Closed
fix(cli): show ⌥T instead of alt+T on macOS for thinking expansion#3Alex-ai-future wants to merge 549 commits into
Alex-ai-future wants to merge 549 commits into
Conversation
…enLM#5285) detectImageMime treated any data starting with the four RIFF bytes as image/webp, but RIFF is a generic container also used by WAV and AVI. A non-WebP RIFF file (e.g. a WAV renamed to .webp) therefore passed the magic-byte check in validateImagePath, which exists to confirm a file's real type. Verify the 'WEBP' marker at bytes 8-11 as well, matching the stricter check already in core's imageTokenizer and the earlier full-signature PNG fix.
* feat(channel): add QQ Bot channel adapter Add @qwen-code/channel-qqbot package implementing QQ Bot WebSocket Gateway connection via the official QQ Bot API. Supports: - WebSocket Gateway (HELLO/IDENTIFY/HEARTBEAT/DISPATCH/RECONNECT) - C2C single chat (C2C_MESSAGE_CREATE) - Group @mention (GROUP_AT_MESSAGE_CREATE) — code path exists, unverified - Streaming output via msg_id + msg_seq multi-block sending - Auto-reconnect with exponential backoff - Sandbox environment toggle TODO (technical debt acknowledged): - Group chat not verified end-to-end - Single-file architecture (should split into gateway/send/auth modules like weixin channel) - No tests (weixin has send.test.ts + media.test.ts) - No typing indicator (onPromptStart/onPromptEnd not yet implemented) - No channel instructions injection in connect() - No structured error types Closes QwenLM#5201 * feat(qqbot): add QR login, group chat support with typed events - Add QR code login via @tencent-connect/qqbot-connector with credential persistence - Add Intent constants for C2C (1<<12) and GROUP_AT_MESSAGE (1<<25) - Use QQGroupMessageEvent type in handleGroup instead of cast - Remove resolved TODO comments for group chat verification - Add msg_seq to send error log for debugging * fix(qqbot): address PR review — lint errors, token refresh, security - Use bracket notation for Record<string, unknown> to fix TS4111 lint errors - Add chmodSync(credsFile, 0o600) for credential file permissions - Implement token refresh at 80% TTL with expires_in tracking - Fix RECONNECT opcode: use code 4000 + serverRequestedReconnect flag - Fix connect() Promise: reject on close before READY via connectReject - Log empty-token case in sendMessage, drain response body on error - Clear chatTypeMap/replyMsgId/msgSeqMap in disconnect() - Capture msgId at send-time to avoid race on replyMsgId - Switch channel-registry.ts to Promise.allSettled (isolated channel failures) - Add chatId validation (isValidChatId) to prevent SSRF * fix(qqbot): add qqbot to build order, fix ESLint default-case - Add packages/channels/qqbot to scripts/build.js buildOrder (CLI imports @qwen-code/channel-qqbot but it wasn't being built) - Add default case to handleGatewayMessage switch * feat(qqbot): prepend sender name in group messages for shared context When sessionScope is set to 'thread', all group members share one session. Prepending [senderName] helps the agent distinguish who said what in the shared context. * feat(qqbot): cross-server context continuation via SessionRouter persistence - Persist SessionRouter mappings to disk via sessionsPath, surviving daemon restarts - Persist QQ routing state (chatTypeMap, replyMsgId, msgSeqMap) to {name}-state.json - Backup/restore global sessions.json on disconnect/connect to survive start.ts cleanup - fixRestoredSessions() workaround for ACP LoadSessionResponse missing sessionId - READY handler delays resolve() until restoreSessions() completes, preventing race * feat(qqbot): add Session Resume + reconnect retry resilience - Support WS session resume (RESUME opcode 6) on reconnect, falling back to full IDENTIFY when session is invalid - Add reconnectWithRetry() loop: retries gateway fetch up to 5x with exponential backoff, then schedules 60s fallback retry (fixes silent death after GW HTTP 500) - connect() now retries up to 3 times on initial failure - Bump maxReconnectAttempts from 10 to 20 - Refresh token before each reconnect attempt * fix(qqbot): address review feedback from wenshao - fixRestoredSessions: use entry.target directly instead of tt.get(undefined) (fixes first restored session routing to wrong conversation when 2+ sessions) - scheduleTokenRefresh: retry in 60s on token refresh failure, not just log - sendMessage: move saveQQState() after chunk loop, avoid redundant disk I/O - handleGroup: drop message when group_openid is missing instead of falling back to author.id (which would cause 404 on group message send) * fix(qqbot): address 3rd review from doudouOUC (12 issues) - QWEN_HOME: use getGlobalQwenDir() instead of homedir() - name sanitization: prevent path traversal in file paths - fetch timeouts: AbortSignal.timeout(15s) on all 3 fetch calls - TOCTOU: writeFileSync with {mode: 0o600} instead of chmodSync after - msg_seq gaps: only increment seq on send success, break on failure - message dedup: seenMessages Map with 5min TTL cleanup timer - disconnect: set disposed flag + flushQQState sync + clear timers - heartbeat ACK: track lastHeartbeatAck, force close on 2x interval timeout - reconnect exhaustion: FATAL log when max attempts reached post-connect - debounced saveQQState: 500ms debounce, flush on disconnect - handleGroup: skip [senderName] prefix for slash commands, log for audit - disposed guard: connectGateway checks disposed before creating WS * fix(qqbot): robustness round — RESUMED, token expiry, SSRF, disposed, typing stubs - Handle RESUMED event on RESUME success (start heartbeat, restore sessions) - Check token expiry before sendMessage, refresh if expired - Tighten isValidChatId regex (remove . and /) to close path traversal - Reset disposed flag in connect() for reusability - Add onPromptStart/onPromptEnd stubs (QQ Bot has no typing API) - Add robustness comments for splitText surrogate pairs, restoreQQState corruption, and senderId identity fragmentation across contexts * refactor(qqbot): split into modules — api, accounts, login Extract HTTP calls, credential I/O, and QR login into separate files matching the weixin channel's architecture: - api.ts: fetchAccessToken, fetchGatewayUrl, getApiBase, sendQQMessage - accounts.ts: getCredsFilePath, loadCredentials, saveCredentials - login.ts: qrCodeLogin (qrConnect wrapper) QQChannel.ts drops inline fetch/credential/qrConnect logic and imports from the new modules. Net -41 lines in the adapter. * feat(qqbot): markdown message support (msg_type: 2) Detect markdown syntax in AI responses and send as msg_type=2 with markdown.content field instead of plain-text msg_type=0. Detection covers headers, code blocks, bold, italic, strikethrough, inline code, links, and lists via a single regex. * fix(qqbot): defensive patches from complete review - reconnectWithRetry: guard against disposed channel to prevent infinite loop - handleGroup: broaden @mention regex to match both legacy <@!id> and V2 <@openid> - handleGroup: set isReplyToBot=true (every group msg is an @mention) - fixRestoredSessions: document fragile private-field access - saveCredentials: correct TOCTOU claim in comment - hasMarkdownSyntax: document false-positive trade-off * fix(qqbot): guard against empty content in C2C and group handlers - handleC2C: return early when event.content is null/empty (image/sticker msgs) - handleGroup: return early when cleanText is empty after @mention stripping * fix(qqbot): close remaining review gaps — disposed guard, connectReject, token retry, RESUMED restore * fix(qqbot): address wenshao review — RESUME restore removal, disposed guards, timer tracking, logging, heartbeat floor, requiredConfigFields, channel-registry error labels * fix(qqbot): markdown fallback to plain text on rejection * docs(qqbot): clarify markdown permission — Open Platform has no gate, FAQ is a different platform * feat(qqbot): add Ark (msg_type=3) and Media (msg_type=7) message support - types.ts: ArkKV, ArkPayload, FileType, MediaUploadRequest/Response, MediaPayload - api.ts: uploadQQMedia() — file upload for rich media - QQChannel.ts: sendArk(chatId, templateId, kv) + sendMedia(chatId, fileType, url, text?) - C2C/group upload paths separated (file_info not interchangeable) - file_type=4 (文件) blocked for groups per QQ API - Embed (msg_type=4) skipped — QQ频道专用, not available for Bot Open Platform * feat(qqbot): auto-route !ark / !media commands from LLM text via sendMessage LLM outputs text — the channel now parses structured commands inline: !ark(24, #TITLE#=标题, #META_DESC#=描述) !media(image, https://example.com/photo.jpg, caption text) parseArkCommand / parseMediaCommand extract at sendMessage entry; normal text/markdown flow unchanged. * feat(qqbot): inject channel instructions for ark/media commands Sets config.instructions on connect() so the LLM learns about: !ark(template_id, key=val, ...) — 3 default templates (23/24/37) !media(type, url, [caption]) — image/video/voice/file Fixes known debt: 'No channel instructions'. * feat(qqbot): gate ark/media behind config flags (enableArk/enableMedia) Both features default to false — opt-in via settings.json: channels.my-qq.enableArk = true channels.my-qq.enableMedia = true Instructions injected conditionally; command routing gated per-flag. * refactor(qqbot): extract resolveRoute() to eliminate duplication across sendMessage/sendArk/sendMedia disposed check, token refresh, chatId validation, sandbox path selection now in one place. All three methods call resolveRoute() instead of repeating the same 15-line preamble. * chore(qqbot): remove Ark and Media message support Remove !ark() / !media() text parsing, sendArk/sendMedia methods, uploadQQMedia, and all related types. The text-parsing approach was too fragile against LLM output formatting. Only text/markdown messaging remains. * fix(qqbot): robustness patches for review findings - Add { mode: 0o600 } to all writeFileSync calls (state/session files) - Guard against stale WebSocket close event nuking new connection - Add isReconnecting guard to prevent parallel reconnectWithRetry chains - Reset isReconnecting flag in READY, RESUMED, and exhaustion paths * docs(channel): add QQ Bot user documentation Add user-facing documentation for the QQ Bot channel adapter: - New docs/users/features/channels/qqbot.md covering setup, configuration, QR code login, group chat, Markdown support, token management, connection resilience, and troubleshooting - Update docs/users/features/channels/_meta.ts to include QQ Bot in nav - Update docs/users/features/channels/overview.md to reference QQ Bot across the intro, quick start, type options, slash commands, and the media platform differences table * docs(qqbot): fix prerequisites — QR login needs no developer account QR code login via qrConnect() does not require a developer account or manual app registration. First qwen channel start is all you need. * docs(qqbot): emphasize QR login, keep developer portal as secondary path Both paths work (config → persisted file → QR scan), confirmed against fetchToken() code. Reposition QR code login as the primary setup flow, remove redundant tips/troubleshooting entries. * docs(qqbot): remove Images and Files section — not supported in channel code handleC2C/handleGroup both skip messages with no text content. No media download or upload logic exists in this channel adapter. * test(qqbot): add unit tests for send utilities Add vitest test suite for QQ Bot channel following the weixin channel testing patterns. Extract isValidChatId, hasMarkdownSyntax, and splitText as exported module-level functions to enable direct testing. - 27 tests covering: chatId SSRF validation, Markdown syntax detection, and text chunking for QQ's 2000-char message limit - Add vitest.config.ts and test script to qqbot package - Register qqbot in root vitest workspace projects Refs: QwenLM#5202 * test(qqbot): add sendMessage flow tests with mocked API Follow the weixin sendImage test pattern: mock sendQQMessage and channel-base dependencies to test sendMessage end-to-end. - C2C/group routing verification - Markdown msg_type=2 vs plain text msg_type=0 - Markdown rejection fallback to plain text - Disposed guard and error-stop behavior - msg_id + msg_seq tracking for multi-chunk streaming 9 new tests, 36 total (all passing) * test(qqbot): fix review issues — add missing edge cases Self-review fixes: - Fix misleading test name: 'returns early when chatId not in chatTypeMap' → 'defaults to C2C path for unknown chatId' (code doesn't return early) - Add SSRF validation test: sendMessage rejects '../traversal' chatId - Add network error test: thrown sendQQMessage caught by try/catch - Add token expiration test: expired token + failed refresh → early return - Hoist mockFetchAccessToken and set default resolved value in beforeEach to prevent silent undefined-access failures in accidental token-refresh paths 39 tests, all passing * test(qqbot): add api and accounts unit tests Add api.test.ts (13 tests) and accounts.test.ts (8 tests) following weixin channel vitest patterns: vi.hoisted() mocks, vi.mock() module replacement, and dynamic import() after mock setup. api.test.ts covers getApiBase, sendQQMessage, fetchAccessToken, and fetchGatewayUrl — including HTTP errors, missing fields, and request body format. accounts.test.ts covers getCredsFilePath, loadCredentials (missing file, corrupt JSON, missing fields, valid data), and saveCredentials (dir creation + 0o600 permissions). All 60 tests pass (39 existing + 21 new). tsc --build and eslint clean. * chore(qqbot): suppress CodeQL ReDoS false positives Add codeql[js/polynomial-redos] suppression comments for two regexes flagged by CodeQL: - hasMarkdownSyntax(): input is LLM-generated reply text, never attacker-controlled in Qwen Code Channel context. - handleGroup(): <@...> prefix is injected by QQ servers; openid is assigned by QQ, not attacker-chosen. Both paths have no practical exploit vector — an adversary would need to either control an LLM's output or register a malicious openid with QQ, neither of which is achievable. * fix(qqbot): allow QR-code-only login and guard qrConnect return - requiredConfigFields: [] — fetchToken() already resolves credentials from config → persisted file → QR fallback chain. Blocking at config validation prevented QR-code-only users from starting the channel. - qrCodeLogin(): add bounds check for empty qrConnect() return value. If the external library returns an empty array, throw descriptive error instead of crashing with TypeError on creds.appId. * chore(qqbot): add comments for requiredConfigFields and qrConnect guard - index.ts: explain why requiredConfigFields is empty — fetchToken() already resolves credentials via config → file → QR fallback chain. Requiring appID/appSecret at config level would block QR-only users from reaching the fallback through the built-in channel path. - login.ts: clarify qrConnect() guard is a defensive robustness patch, not a response to an observed failure. Verified by removing appID from config and running qwen channel start — QR login triggers correctly and returns valid credentials. * fix(qqbot): replace quadratic regexes with linear patterns, remove failed suppress comments * fix(qqbot): split hasMarkdownSyntax into individual tests to pass CodeQL * fix(qqbot): replace markdown link regex with indexOf to eliminate CodeQL ReDoS
…wenLM#5311) Co-authored-by: aspnmy <[email protected]>
- Show session title in terminal window instead of fixed 'Qwen - <folder>' - Clear terminal title on exit so it reverts to shell default (empty OSC sequence without 80-char padding) - Add try/catch around exit handler to suppress EPIPE when stdout is already closed - Extend multiplexer detection to include Zellij (ZELLIJ) and dvtm (DVTM) - Chain title callbacks so AppContainer preserves Session's existing ACP notification callback instead of overwriting it - Revert terminal title to static fallback when showStatusInTitle is toggled off at runtime - Reuse sanitizeForOsc for BiDi/RTL directional override protection - Update tests for new behavior Co-authored-by: 俊良 <[email protected]>
…#5328) modalityDefaults only marks the multimodal variants (qwen3.6-plus, kimi-k2.5) as image/video capable; qwen3.6-flash and kimi-k2.6 fall through to the text-only default. The Token Plan and Idealab presets still declared image/video for them, overstating what the models accept. Drop the stray modalities so they match the defaults, as a follow-up to the DeepSeek cleanup in QwenLM#5268.
…4808) Add a bundled skill at .qwen/skills/desktop-pet/ that generates chibi pixel-art desktop pet companions for any character the user names (F1 drivers, anime characters, celebrities, animals, etc.). - SKILL.md: agent instructions for character research, color palette design, spritesheet generation, and pet activation - scripts/gen_spritesheet.py: parameterized generator producing a 1536x1872 RGBA spritesheet (8x9 grid, 192x208 cells) compatible with the pet animation system (9 states, 10 headgear, 5 hair styles) Output goes to ~/.qwen/pets/ for auto-discovery by the custom pet system. Closes QwenLM#4807
…enLM#5287) formatDuration formats durations under a minute with toFixed(1). Values from 59.95s up round to "60.0", so the tool printed "60.0s" (or "60s" with hideTrailingZeros) — not a valid sub-minute reading — instead of "1m". Detect when the rounded value reaches 60 and render it as the minute it rounds to, matching formatDuration(60000) === "1m".
… in the desktop session list (QwenLM#5253)
* feat(stats): expose token usage for cost visibility Persist content-free API token counters and surface daily/monthly summaries plus CSV/JSON export through /stats. Constraint: Issue QwenLM#4479 requested CLI token visibility with monthly/model breakdowns and export while coordinating with QwenLM#4252/QwenLM#4182.\nRejected: Add a separate top-level token command | /stats keeps related statistics in one surface.\nConfidence: high\nScope-risk: moderate\nDirective: Keep TTFT/TPS generation timing and memory diagnostics outside this token-usage surface unless their issues explicitly broaden scope.\nTested: npx vitest run src/services/tokenUsageService.test.ts src/telemetry/loggers.test.ts; npx vitest run src/ui/commands/statsCommand.test.ts src/ui/hooks/useAutoAcceptIndicator.test.ts src/ui/components/AutoAcceptIndicator.test.tsx; npm run check-i18n --workspace=packages/cli; npm run lint --workspace=packages/cli; npm run lint --workspace=packages/core; npm run typecheck; npm run build; git diff --check\nNot-tested: full integration suite * fix: Address token usage review feedback Tighten persisted token usage so internal prompt traffic and disabled usage statistics do not write history, while surfacing non-ENOENT write failures outside debug logs. Complete the reviewer-requested i18n coverage and regression tests around auto mode notices and best-effort writes. Constraint: Follow-up to wenshao review comments on PR QwenLM#4564. Rejected: Keeping token usage recording outside the internal-prompt gate | It would inflate daily and monthly stats with background prompts. Confidence: high Scope-risk: narrow Directive: Keep /stats token usage scoped to user-visible API responses unless future requirements explicitly include background traffic. Tested: npx vitest run src/services/tokenUsageService.test.ts src/telemetry/loggers.test.ts; npx vitest run src/ui/hooks/useAutoAcceptIndicator.test.ts src/ui/commands/statsCommand.test.ts; npm run typecheck; npm run lint --workspace=packages/core; npm run lint --workspace=packages/cli; npm run check-i18n --workspace=packages/cli; npm run build; git diff --check Not-tested: Full repository test suite * fix(stats): satisfy token usage review contract Constraint: wenshao review required consistent token stats, exports, i18n, and best-effort logging behavior. Rejected: Change cached-token labeling | keeping cached tokens included in input preserves the accepted /stats display contract. Confidence: high Scope-risk: narrow Directive: Keep cached tokens included in input whenever cached-only metadata is used in total fallback. Tested: cd packages/core && npx vitest run src/services/tokenUsageService.test.ts src/telemetry/loggers.test.ts Tested: cd packages/cli && npx vitest run src/ui/commands/statsCommand.test.ts src/i18n/mustTranslateKeys.test.ts Tested: npm run check-i18n --workspace=packages/cli; npm run typecheck; git diff --check Not-tested: full integration suite * Refine token usage recording after review Constraint: Address wenshao's latest PR QwenLM#4564 review suggestions without expanding the /stats command surface. Rejected: Keeping synchronous token-usage writes | sync I/O remains on the API response hot path. Confidence: high Scope-risk: narrow Directive: Keep token usage persistence best-effort and gated by explicit usage-statistics enablement. Tested: cd packages/core; npx vitest run src/services/tokenUsageService.test.ts src/telemetry/loggers.test.ts; cd packages/cli; npx vitest run src/ui/commands/statsCommand.test.ts; npm run typecheck; npm run build; npm run lint --workspace=packages/core; npm run lint --workspace=packages/cli; git diff --check Not-tested: Full repository test suite * fix(stats): avoid silent zero usage on read failures Propagate token usage read failures through the existing /stats error path while keeping missing usage files empty, and remove the unreachable telemetry wrapper catch. Constraint: PR QwenLM#4564 review requested user-visible read failures, full i18n for export errors, and removal of dead telemetry catch code. Rejected: Adding warning fields to TokenUsageSummary | It would expand the JSON/export schema when the existing command error path already fits read failures. Confidence: high Scope-risk: narrow Directive: Keep jsonl.read default swallowing behavior for existing session/history callers unless a user-visible caller opts into rethrowing non-ENOENT errors. Tested: npx vitest run src/utils/jsonl-utils.test.ts src/services/tokenUsageService.test.ts src/telemetry/loggers.test.ts Tested: npx vitest run src/ui/commands/statsCommand.test.ts Tested: npm run check-i18n --workspace=packages/cli Tested: npx prettier --check changed files Tested: npm run typecheck Tested: npm run lint --workspace=packages/core Tested: npm run lint --workspace=packages/cli Tested: git diff --check Tested: npm run build Not-tested: Full integration test suite * fix: close token usage review gaps Keep the review follow-ups local to token usage accounting and stats export without adding new abstractions. Constraint: Address PR QwenLM#4564 reviewer requests on token usage export/query reuse, write-failure stderr noise, and invalid-record diagnostics. Confidence: high Scope-risk: narrow Directive: Keep token usage writes best-effort and avoid noisy stderr loops for repeated local failures. Tested: git diff --check; prior targeted core/cli tests, typecheck, and lint passed for this working tree. Not-tested: Full repository test suite. * fix(stats): address token usage review feedback * Update packages/core/src/services/tokenUsageService.ts Co-authored-by: Shaojin Wen <[email protected]> * test(core): keep token usage stderr assertion current Keep the repeated write-failure regression test aligned with the runtime wording that the PR now emits. Constraint: PR QwenLM#4564 CI failed after the implementation wording changed to "since last log". Rejected: Reverting the implementation wording | it is the latest PR behavior and the failure is test-only. Confidence: high Scope-risk: narrow Tested: cd packages/core && npx vitest run src/services/tokenUsageService.test.ts Not-tested: full repository test suite * fix(stats): clarify export review edge cases Address the remaining PR review polish without changing token accounting, export formats, or path containment behavior. Constraint: Review 4452925552 requested narrow documentation, ENOENT wording, and NOTICES cleanup only. Rejected: Broader merge-conflict rework | GitHub currently reports the PR as mergeable, and the requested fixes are review polish. Confidence: high Scope-risk: narrow Directive: Keep token usage records content-free and preserve export path validation semantics except for the final ENOENT message. Tested: cd packages/core && npx vitest run src/services/tokenUsageService.test.ts; cd packages/cli && npx vitest run src/ui/commands/statsCommand.test.ts; npm run check-i18n --workspace=packages/cli; npm run typecheck; git diff --check on changed code and i18n files Not-tested: Full test suite not run. * fix: address token usage review feedback * fix(stats): harden token usage CSV export * fix(stats): remove unrelated auto mode noise --------- Co-authored-by: Shaojin Wen <[email protected]>
…in (QwenLM#5185) * fix(plan-gate): isolate gate agent AbortSignal from parent signal chain The Plan Approval Gate's review agent inherited the parent round's AbortController signal. When transient parent-side issues occurred (stream NO_FINISH_REASON retries, round cleanup), the abort cascaded into the gate agent, causing it to terminate with mode=CANCELLED. The runAgentWithRetry loop then saw signal.aborted=true and immediately skipped all remaining retry attempts, leaving the user stuck in plan mode with a repeated 'Gate review agent unavailable' message. Changes: - runGateAgent: create an independent AbortController with its own timeout (5 min) instead of inheriting the parent signal. This isolates the gate agent from transient parent-side aborts. - runAgentWithRetry: add a 1s delay between retries to allow transient issues to settle. - Improved log messages to distinguish attempt numbers in retry loop. Fixes: exit_plan_mode getting stuck when NO_FINISH_REASON stream errors cascade through the abort signal chain. * fix(plan-gate): address review comments - Fix docstring: parent signal is pre-checked only, not monitored during execution. Matches the actual isolation behavior. - Remove signal.aborted check from retry loop. The gate agent is now fully signal-isolated with its own timeout, so parent-side aborts (typically transient) should not short-circuit retries. The caller discards the result if the parent is truly gone. - Replace plain setTimeout delay with abortableSleep() that returns early if the parent signal aborts during the 1s wait. Prevents wasted shutdown time on genuine cancellation. - Fix timeout comment: gate agent typically completes in 1-2 minutes; the 5-minute ceiling matches runConfig.max_time_minutes. * fix(plan-gate): address follow-up review comments - Remove parentSignal.aborted pre-check from runGateAgent. The hard-fail conflicted with the isolation goal: a transient parent abort would throw, abortableSleep would resolve immediately, and retries would spin in a rapid loop. The gate agent's own 5-minute timeout is the sole cancellation mechanism; the parent signal is intentionally unused. - Fix race window in abortableSleep: attach the abort listener first, then re-check signal.aborted to handle the case where abort fires between the initial check and listener registration. Eliminates the edge case where the sleep would wait the full duration despite an already-aborted signal. * fix(plan-gate): use existing delay() for abort-aware retry backoff Replace custom abortableSleep with the existing delay() utility from utils/retry.ts, which was already battle-tested for exactly this pattern: - delay() REJECTS (not resolves) when the signal is aborted, so the retry loop can catch the rejection and cleanly exit — no rapid-fire retry spin after cancellation. - delay() already handles the TOCTOU race window (attach listener first, re-check signal.aborted after) — addresses the second review comment. - Also exports delay() from retry.ts since planApprovalGate needs it. This eliminates the risk of abortableSleep resolving early during abort and immediately proceeding to another gate agent attempt, which would cause rapid successive retries and log spam. * fix(plan-gate): address final review comments - Add entry-time signal.aborted check to avoid launching gate agent on obvious cancellations - Only delay between retries when attempt < MAX_AGENT_RETRIES (not after final failure) - Clarify timeout comment: 5-min ceiling is fixed, coincides with runConfig but independent * fix(plan-gate): address Copilot review round 5 - Clarify entry-time comment: it's the only synchronous pre-run check; delay() still provides abort-awareness between retries. - Guard gateAbortController.abort() in finally: only abort if not already aborted, avoiding unnecessary abort events on success.
Replace the oversized opt-in refactor with the minimal approach from review feedback: keep main's default-on language append, add skipOutputLanguagePreference for internal structured queries, and remove the conflicting English/Chinese-only hints from session title/recap prompts so the appended preference can take effect. Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: JerryLee <[email protected]>
* fix(cli): avoid stale git branch watcher setup * Align with origin/main: drop incidental merge-formatting drift on unrelated files --------- Co-authored-by: Shaojin Wen <[email protected]>
…#5336) detectExtensionFromMagic matched the bare "RIFF" prefix and always returned .wav, so WebP and AVI files (which share the RIFF container) were mislabeled as .wav. Disambiguate by the four-character form tag at bytes 8-11, mirroring the existing weixin WebP signature check.
…wenLM#4993) * feat(input): move physical cursor to visual cursor for IME input (QwenLM#4652) * feat(input): add useCursor hook for IME physical cursor tracking * refactor(input): optimize cursor positioning effect * feat(input): move setCursorPosition to render phase for immediate cursor positioning * fix(input): calculate absolute cursor position by walking yoga tree * fix(input): use addLayoutListener instead of useCursor for zero-jitter cursor positioning * perf(input): stable addLayoutListener subscription and skip redundant cursor updates * fix(input): revert lastPos dedup that broke cursorDirty one-shot flag * feat(input): use patch-package to expose Ink internals for IME cursor positioning * fix(input): address review feedback — prefixWidth, remove useBoxMetrics, pin ink version Co-authored-by: Shaojin Wen <[email protected]> --------- Co-authored-by: Shaojin Wen <[email protected]> * fix(cli): restore approval-mode reference dropped in QwenLM#4600 QwenLM#4600 unified the approval-mode prompt styling into approvalModePromptStyle and removed the showAutoAcceptStyling / showYoloStyling constants, but left a dangling showYoloStyling reference in the prefixWidth calculation. This broke the tsc build (TS2304: Cannot find name showYoloStyling). npm run bundle (esbuild) skips type-checking, so the break only surfaced under npm run build / tsc --build. Replace it with approvalMode === ApprovalMode.YOLO, matching how the rest of the file branches on approval mode after QwenLM#4600. Both ternary arms stay 2 because every approval-mode prefix is a single char (getApprovalModePromptStyle returns prefix > or *), so the rendered prefix width and cursor positioning are unchanged. --------- Co-authored-by: yao <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> Co-authored-by: LaZzyMan <[email protected]> Co-authored-by: Shaojin Wen <[email protected]>
* fix(cli): close @path completion dropdown on Enter accept When accepting an @path suggestion with Enter, the dropdown did not close for folder paths because handleAutocomplete intentionally omits the trailing space (to allow continued Tab-completion deeper into the directory). Without the space, the @ completion pattern re-matches and re-shows the dropdown. Fix: call completion.resetCompletionState() after accepting on Enter. Tab is intentionally excluded so users can still continue navigating deeper into directories. Also adds two regression tests: - Enter after @path accept resets completion state - Tab after @path accept does NOT reset completion state Signed-off-by: Alex <[email protected]> * fix(cli): suppress @path completion re-open after Enter accept The previous fix called resetCompletionState() on Enter, but the dropdown re-opened because useCommandCompletion's useEffect sets showSuggestions(true) when suggestions.length > 0, which happens asynchronously after useAtCompletion re-globs for the still-active @token (folder paths have no trailing space). Add a dismissed flag to the completion state: - resetCompletionState() now sets dismissed = true - The showSuggestions useEffect skips re-opening when dismissed - dismissed is cleared when the completion query changes (user types) This ensures the dropdown stays closed after Enter accept until the user modifies the input again. Tests: 153 passed in InputPrompt.test.tsx, 26 passed in useCommandCompletion.test.ts, typecheck clean. Signed-off-by: Alex <[email protected]> * fix(cli): address review feedback on dismissed completion guard - Move setDismissed out of resetCompletionState: resetCompletionState is called from 6+ sites (submit, history, Escape, Ctrl+C), not all are user dismissals. New dismissCompletion() method sets dismissed flag + resets state, called only on Enter accept. - Keep dismissed/internal state private to useCompletion. Expose dismissCompletion() and clearDismissed() instead of raw dismissed/setDismissed in UseCommandCompletionReturn. - Remove setDismissed from useEffect dependency arrays (stable useState setter, never called in body). - Add test for dismissCompletion being called on Enter accept. Reviewed-by: wenshao Signed-off-by: Alex <[email protected]> * restore removed comments in useCompletion.ts The original navigateUp/navigateDown callbacks had explanatory comments describing the wrap-around and scroll adjustment logic. Restore them. Signed-off-by: Alex <[email protected]> * fix(cli): fix dismissed guard bypass after accept+dismiss DragonZhang identified that clearDismissed fires in the same render cycle as dismissCompletion, because accepting a suggestion also changes the query. The dismissed flag gets reset to false immediately, and on the next render the guard is bypassed — dropdown re-opens. Fix: move the query-change effect into useCompletion and use a skipNextClearRef to prevent clearing dismissed after an accept: - useCompletion: add skipNextClearRef, set to true in dismissCompletion - useCompletion: accept query option, run query-change effect internally - useCompletion: skip clearDismissed if skipNextClearRef is true - useCommandCompletion: reorder useMemo before useCompletion to compute query first; pass query to useCompletion; remove the moved effect - useCommandCompletion: remove clearDismissed from destructuring This ensures dismissed stays true after accept+dismiss, and only gets cleared when the user actually types more. Reviewed-by: DragonZhang Signed-off-by: Alex <[email protected]> * fix(cli): allow null query in UseCompletionOptions to fix CI build The query from useCommandCompletion's useMemo is string | null, but UseCompletionOptions.query was typed as string | undefined. Reviewed-by: CI Signed-off-by: Alex <[email protected]> * fix(cli): fix prevQueryRef type to accept null from options.query TS2322: prevQueryRef was typed as string | undefined but options.query is string | null | undefined. The assignment on line 89 failed tsc --build (CI). tsc --noEmit didn't catch this because it doesn't emit. Signed-off-by: Alex <[email protected]> * fix(cli): address Jun 14 review comments on @path completion fix - Add setDismissed(false) to resetCompletionState so all reset paths (ESC, submit, mode change) recover dismissed state - Reorder dismissCompletion to set dismissed after resetCompletionState to prevent the reset from clearing it immediately - Remove clearDismissed from public API (no external consumer uses it); inline setDismissed(false) in the query-change effect - Add useCompletion.test.ts with 13 tests covering the dismissed / skipNextClearRef state machine Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): gate dismissCompletion on isDirectory to preserve slash-command suggestions Only call dismissCompletion() when accepting a directory suggestion, preventing slash-command sub-suggestions from being suppressed after Enter accept. Add assertion to Tab test verifying dismissCompletion is not called. Addresses review feedback from @wenshao. Signed-off-by: AlexHuang <[email protected]> Signed-off-by: Alex <[email protected]> * fix(cli): scope dismissCompletion to AT-mode directory suggestions Add completionMode === CompletionMode.AT guard so slash-command directory suggestions are not affected by dismissCompletion. Add negative assertion for non-directory Enter test to prevent regression of the isDirectory check. Addresses review feedback from @wenshao. Signed-off-by: AlexHuang <[email protected]> Signed-off-by: Alex <[email protected]> * fix(cli): expose completionMode in UseCommandCompletionReturn The previous commit referenced completion.completionMode in InputPrompt but the property was not exposed from useCommandCompletion, causing a TypeScript build error (TS2339) in CI. Add completionMode to the UseCommandCompletionReturn interface and return object. Update useExportCompletion.test.ts mock accordingly. Signed-off-by: AlexHuang <[email protected]> Signed-off-by: Alex <[email protected]> --------- Signed-off-by: Alex <[email protected]> Signed-off-by: AlexHuang <[email protected]> Co-authored-by: Qwen-Coder <[email protected]>
…M#5089) (QwenLM#5745) * revert(core): revert Protocol enum & model-identity decoupling (QwenLM#5089) Reverts the structural changes from QwenLM#5089 back to the pre-QwenLM#5089 shape: AuthType stays a fixed enum (not `string`), the Protocol enum is removed, modelProviders is `Record<authType, ModelConfig[]>` again (not `{ protocol, models }`), and createContentGenerator dispatches on authType. The v4->v5 settings migration is removed and SETTINGS_VERSION reverts to 4. Features merged on top of QwenLM#5089 are kept and re-adapted to the old enum+array structure (not reverted): - QwenLM#5632 fastOnly/voiceOnly model flags (test fixtures reshaped to arrays) - QwenLM#5638 workspace provider defaults (readProviderModels already tolerates both shapes; test fixtures reshaped to arrays) - QwenLM#5729 active-runtime-model listing (pre-QwenLM#5089 getAllConfiguredModels already enumerates Object.values(AuthType), so the runtime model is included natively) - QwenLM#5728 ACP set_config_option deterministic provider fixture (reshaped to array; the flake fix is preserved) KNOWN DOWNGRADE CAVEAT: settings already migrated to $version:5 (shipped in v0.19.0) retain the v5 `{ protocol, models }` modelProviders shape, which the reverted ModelRegistry consumes as an array. Such settings will throw on load until re-configured. A v5->v4 downgrade guard/migration is a separate follow-up if backward compatibility for migrated users is needed. * feat(cli): add v5->v4 settings downgrade migration for QwenLM#5089 revert After reverting QwenLM#5089, settings already migrated to $version:5 (shipped in v0.19.0) carry a modelProviders `{ protocol, models }` shape that the reverted v4 readers consume as arrays, throwing "models is not iterable" on load. This adds the inverse migration so those configs auto-converge to v4 on load (the user-facing "automatically migrate $version:5 to 4"). - V5ToV4Migration: unwraps each modelProviders `{ protocol, models }` back to its `models` array, drops the now-implicit protocol (warning only when the explicit protocol differs from the key-derived one), and resets $version to 4. - DOWNGRADE_MIGRATIONS keeps the downgrade out of the ascending forward ALL_MIGRATIONS chain (preserving its invariants); runMigrations and needsMigration consider both via a combined convergence set. - needsMigration now gates on `=== SETTINGS_VERSION` instead of `>=`, so a newer-but-handled version (v5) is reported as needing migration while a genuinely unknown newer version (v6+) is still left untouched. Covered by unit tests for the migration, the framework wiring, and an end-to-end loadSettings downgrade-on-load test. * fix(test): align integration settings-version constant with reverted v4 The integration suites hard-coded CURRENT_SETTINGS_VERSION = 5 (introduced by QwenLM#5676), which mismatched the reverted SETTINGS_VERSION = 4 and failed the migration assertions ($version now writes 4, not 5). Revert the constant to 4 in both settings-migration and qwen-config-dir integration tests. Verified: QWEN_SANDBOX=false vitest run --root ./integration-tests cli/settings-migration.test.ts cli/qwen-config-dir.test.ts → 21 passed. * fix: harden v5-era settings handling on the QwenLM#5089 revert path Addresses /qreview feedback on the revert: - vscode findOpenaiModels: restore read-side tolerance for the V5 { protocol, models } shape. The extension reads/writes settings.json without running the CLI v5->v4 migration, so a not-yet-downgraded $version:5 file would otherwise return [] and silently drop existing OpenAI models on the next write. (Critical) - modelRegistry.registerAuthTypeModels: guard against a non-array provider value (skip + warn) instead of throwing an opaque "models is not iterable" — covers hand-edited or unmigrated files the downgrade misses. - needsMigration JSDoc: update the stale ">= SETTINGS_VERSION" wording to match the "=== SETTINGS_VERSION, else fall through" logic the downgrade path depends on. - settings.test.ts: also assert the v5->v4 downgrade is persisted to disk (.tmp write-back), not just the in-memory merged result. Adds tests for the registry guard and the vscode V5 read tolerance. * fix(core): break contentGenerator import cycle + cover reverted error paths Addresses /review suggestions on the revert: - contentGenerator: import PROVIDER_SOURCED_FIELDS from constants.js (where it is actually defined) instead of modelsConfig.js, breaking the runtime import cycle contentGenerator -> modelsConfig -> contentGenerator. constants.js only references contentGenerator at the type level, which is erased at runtime, so no cycle remains. - contentGenerator.test: add coverage for the two authType error paths the revert restored (missing authType -> "must have an authType"; unknown authType -> "Unsupported authType"), which QwenLM#5089's protocol-based tests had replaced. Neither was covered before. The acpAgent z.nativeEnum(AuthType).parse(methodId) suggestion is left as-is: that line is byte-identical to pre-QwenLM#5089, so it is pre-existing behavior the revert faithfully restores rather than a regression of this PR.
…wenLM#5732) * ci: check out PR head SHA to avoid missing merge-ref checkout failures * ci: skip coverage comment on cancelled runs to free concurrency slot post_coverage_comment used if: always(), so on a cancelled run it still ran on the hosted pool. During rapid pushes the cancelled run's coverage job sat queued for a hosted runner, keeping the run non-terminal and holding the branch concurrency slot — so the next push's required Test (ubuntu-latest) check waited behind it (observed ~9 min, with thrash). Switch to !cancelled(): coverage still posts on test failure, but a cancelled run releases the slot immediately. Its coverage would be stale anyway. * ci(triage): route /triage comment runs to the ECS pool `/triage` comments trigger the authorize+triage jobs on hosted runners, which queue behind CI during peak hours (~2 min vs 4 s off-peak observed). Route the comment-triggered triage job to the ECS self-hosted pool, which is not bound by the GitHub-hosted concurrency limit. Safe scope: only `issue_comment` events; the job is read-only and checks out the base ref (never PR-head code). Gated on the same MAINTAINER_ECS_RUNNER_DISABLED kill-switch as `authorize`, so a disabled ECS pool falls back to hosted. PR/issues/dispatch triggers stay hosted. * ci: fetch PR head by ref name to fix 'not our ref' checkout failure * ci: align Test checkout ref with Lint (add branch_ref fallback) On workflow_dispatch the Test job runs (its if: !cancelled() overrides the implicit success() gate when classify_pr is skipped), so its checkout must honor the branch_ref input like Lint does. Without it, dispatch runs validated github.ref in Test while Lint validated the user-supplied branch_ref. * ci: retry merge-ref checkout instead of pinning PR head; drop /triage ECS routing Per review feedback: checking out refs/pull/N/head dropped merge-result validation, and merge queue isn't enabled on main to backstop it. Keep merge-ref semantics (github.ref) for the required Lint/Test checkouts and retry once to absorb the transient merge-ref lag the PR-head change was working around. Also revert /triage routing to ECS: that job runs an agent (sandbox:false, shell/write tools, repo tokens) over untrusted PR/issue text, so it stays on ephemeral hosted runners, not the persistent self-hosted pool. * ci: back off before the merge-ref checkout retry The merge ref (refs/pull/N/merge) is built asynchronously, so a back-to-back retry can hit the still-unbuilt ref again. Sleep 10s before retrying so the retry absorbs the multi-second build lag, not just instantaneous blips.
* chore(release): v0.19.1 * docs(changelog): sync for v0.19.1 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…#5737) * fix(agent): cap fork turns and bubble fork permission prompts A detached fork subagent runs fire-and-forget in the background with no inline UI. Two robustness gaps followed from that: - It was launched with an empty RunConfig (`{} as RunConfig`), so its reasoning loop had no max_turns cap and could burn tokens unbounded. Add FORK_DEFAULT_MAX_TURNS=200 and pass it to AgentHeadless.create. - FORK_AGENT used approvalMode 'default', so in the background launch path shouldBubble was false and permission-gated tool calls were silently auto-denied (the fork couldn't even run its own "commit before reporting"). Switch to 'bubble' so prompts surface to the parent's Background-tasks UI; non-interactive forks are already gated off, so there is no headless regression. Closes QwenLM#5734. Co-authored-by: Qwen-Coder <[email protected]> * fix(agent): cap resumed fork turns --------- Co-authored-by: Qwen-Coder <[email protected]>
…wenLM#5126) * feat(vision-bridge): transcribe images to text for text-only models Add an opt-in "vision bridge": when a text-only primary model receives an image, a configured (or auto-selected) multimodal model transcribes it to text that is then sent to the primary model. Disabled by default. - New UI-agnostic service (visionBridgeService) + image-part utils, with an injection-aware UNTRUSTED fence (trusted guidance first, delimiters defanged) and bounded, clamped settings (maxImages / timeoutMs). - Gate in prepareQueryForGemini: runs only when enabled, the resolved query carries image parts, and the primary model is text-only. - fileUtils.processSingleFileContent: keep image inlineData instead of stripping it to an "unsupported" note when the bridge is enabled, so the image survives @-resolution for the bridge to convert (audio/video still skipped). - Auto-select an image-capable model from registered providers, preferring the same provider as the primary model; user can pin visionBridge.model. - New settings block (default off, hidden from dialog). * docs(vision-bridge): document the vision bridge feature - Add docs/users/features/vision-bridge.md (what/when/how, settings, privacy, failure behavior) and register it in the features nav. - Add a visionBridge settings section to the settings reference. - Add a visionBridge row to the README top-level settings table. * refactor(vision-bridge): stop the turn on any conversion failure Drop the adaptive failure policy (proceed-with-note when the user also asked a text question) in favor of a single rule: any bridge failure stops the turn and surfaces the reason. Simpler failure semantics and a smaller surface; the primary model never answers as if it had seen the image. Removes MEANINGFUL_TEXT_THRESHOLD and buildFailureNote; updates tests and the docs failure-behavior section to match. * fix(vision-bridge): guard Config calls in the gate so partial Configs no-op The bridge gate called config.getVisionBridgeConfig()/getEffectiveInputModalities() unconditionally; test/mock Configs that don't implement them threw inside prepareQueryForGemini, failing useGeminiStream submission tests. Optional-chain both calls so a Config lacking them simply skips the bridge (the real Config always provides them). * fix(vision-bridge): address review — cross-provider auto-select, logging, docs - Auto-select now uses getAllConfiguredModels() so a vision model registered under a different provider/authType than the primary can be picked (runSideQuery resolves its credentials). - Add VISION_BRIDGE debug logging at model resolution, side-query invocation, success, and failure for diagnosability. - Fix stale doc comments (model is auto-selectable when unset; failed results carry no parts after the stop-on-failure change). - Note the clamp ranges in the maxImages/timeoutMs setting descriptions. - Document the interactive-only limitation (agent/headless image reads are not bridged). * feat(vision-bridge): name the bridge endpoint in the egress notice Adopt review follow-up: cross-provider auto-select can route the image to a different endpoint than the primary model, so the conversion notice now names the endpoint host (e.g. "via qwen3.7-plus (dashscope.aliyuncs.com)") alongside the model id, making the data-egress disclosure precise. Adds modelEndpoint to the result and a unit test. * fix(vision-bridge): scope the image-preserve bypass to the interactive @-path Previously, with the bridge enabled, the fileUtils image-preserve bypass fired for ALL processSingleFileContent callers — so agent tool reads (read_file / read_many_files) and headless/ACP runs carried image inlineData to a text-only model (omitted by the converter) instead of the clear "model doesn't support image input" message. The bridge only runs in the interactive path, so this was a silent regression for non-interactive image reads. Thread an explicit preserveUnsupportedImageForBridge flag from the interactive @-resolution path (atCommandProcessor → readManyFiles → processSingleFileContent). Agent tools and headless don't set it, so they keep the prior "Skipped" behavior; only the interactive @-path preserves images for the bridge. Adds tests for both paths and updates the docs limitation note. * test(vision-bridge): cover the useGeminiStream bridge gate Add hook-level tests for the gate that were previously missing: bridge runs and replaces image parts with text (enabled + text-only); turn stops with no model stream when conversion fails; bridge is skipped when the primary model already accepts images; bridge is skipped when disabled. Mocks runVisionBridge and drives @-resolution via the existing handleAtCommand spy. * refactor(vision-bridge): drop unused intentText param; log in the gate - Remove the never-passed intentText parameter from runVisionBridge; intent is derived from the request's non-image text. - Add GEMINI_STREAM debug logging in the gate (match/run + result status). * fix(vision-bridge): preserve provider routing for bridge models Carry the selected auth type and base URL through side queries so duplicate model ids resolve to the exact provider. Add a tmux E2E test plan for the text-primary vision bridge flow. * fix(vision-bridge): keep e2e docs out of gitignore whitelist * fix(core): harden vision bridge review fixes * fix(vision-bridge): address review feedback * chore(vision-bridge): drop unrelated diff churn * fix(vision-bridge): address review hardening * fix(vision-bridge): address remaining review cleanup * fix(vision-bridge): skip unknown primary modalities * fix(vision-bridge): address review hardening gaps * feat(vision-bridge): auto-enable for text-only models * fix(vision-bridge): tighten text-model image handling * fix(vision-bridge): transcribe mid-turn image references * fix(vision-bridge): harden mid-turn bridge handling * fix(vision-bridge): share bridge gate logic * chore(vision-bridge): trim unnecessary review changes * refactor(vision-bridge): slim implementation, scope auto-select to same provider Roughly halve the vision-bridge implementation and fix the auto-select issue that local tmux e2e surfaced. - Auto-select now borrows an image-capable model ONLY on the same provider as the text-only primary (same endpoint, else same auth type); when none exists the bridge stays off. The previous "pick first image-capable" routed to a misclassified OAuth runtime model on a different endpoint that hung the turn. - Revert the per-query provider-hint plumbing (modelAuthType/modelBaseUrl/ onDispatch) from baseLlmClient/sideQuery/modelRegistry/modelsConfig/ content-generator-config; a bare model id already resolves across auth types. - Replace the prompt-injection fence machinery with a one-line untrusted wrapper, the depth-tracking <think> parser with a small regex strip, and drop dead result fields, the per-call maxImages knob, and a discarded validator message. - Collapse the processSingleFileContent overload into a single options arg. Verified locally: core + cli unit suites green, typecheck/eslint/prettier clean, and a real tmux TUI run (text-only qwen3.7-max) auto-selects qwen3.7-plus on the same dashscope endpoint and transcribes successfully. * refactor(vision-bridge): cap bridge intent; drop phantom test fields - Cap the intent sent to the borrowed vision model so @-referenced file contents (also carried in nonImageParts) aren't dumped into the bridge request. The primary model still receives them in full. - Remove imageCount/omittedInvalidCount/omittedCappedCount from test mocks; those fields no longer exist on VisionBridgeResult. Addresses the one real finding from the latest /review pass (full @-file context reaching the bridge model); the other "critical" findings did not hold up (greedy <think> strip is deliberate for nested blocks; debug-log error is standard and opt-in; `error` is a local result field, not an egress). * fix(vision-bridge): handle interleaved <think> blocks; sanitize transcript for terminal - stripThinkTags now removes innermost balanced <think> pairs until stable, so interleaved think/answer output (reasoning vision models) no longer loses the answer text between blocks; nested and unterminated cases still handled. - Strip ANSI/C0 control escapes from the untrusted transcript before it is shown in the terminal notice, so a crafted image can't inject terminal sequences. * fix(file-utils): keep processSingleFileContent positional signature back-compatible processSingleFileContent is part of the public core API (re-exported via index.ts). Restore the legacy (offset, limit, pages) positional overload alongside the new options object so external/positional callers don't break, with a regression test. * test(vision-bridge): cover timeout-only path + transcript escape strip; extend to C1 - Add a test for the timeout-only failure path (the bridge's AbortSignal.timeout fires while the user signal is NOT aborted) — asserts a failed result whose reason is the safe "timed out" message. - Add a test that terminal control/escape chars are stripped from the untrusted transcript shown in the notice (terminal-injection boundary), and extend the strip to C1 controls (U+0080–U+009F) that some terminals interpret. --------- Co-authored-by: Shaojin Wen <[email protected]> Co-authored-by: copilot-swe-agent[bot] <[email protected]>
… mode (QwenLM#5754) Add a Layer 0 deterministic pre-filter in evaluateAutoMode() that blocks destructive git and IaC commands before the LLM classifier runs. This closes the gap where the only defense against `git reset --hard` or `terraform destroy` was the non-deterministic LLM classifier, which can fail due to API unavailability, timeout, or poor judgment. Blocked patterns: - git reset --hard, git checkout -- ., git clean -f*, git stash drop (unless user prompt explicitly mentions discarding work) - git commit --amend (unless the target commit was made this session) - terraform/pulumi/cdk destroy (unless user mentions the target stack) Shell indirection bypass prevention: strips one layer of shell quoting (bash -c "...", sh -c '...') so wrapped destructive commands are still detected. The guard only applies in AUTO mode — YOLO mode is unaffected as it is an explicit opt-out of all safety checks.
* feat(cli): add extension operation polling * fix(cli): harden extension operation polling --------- Co-authored-by: ytahdn <[email protected]>
…M#5757) On VS Code >=1.106 the version-gated `when` clauses hid the primary Activity Bar container in favor of a secondary-sidebar container, so the Qwen Code view flashed and disappeared from the left bar. Drop the secondary-sidebar container and context key and always register the chat view in the Activity Bar, which VS Code lets users move to the secondary sidebar natively.
…LM#5764) collectContextData read apiTotalTokens from uiTelemetryService, a process-global singleton shared by every session in a serve daemon, so each session's /context-usage reported whichever session most recently completed a turn. Read the per-session GeminiChat's count instead (config already in scope), falling back to the global singleton only when no chat is initialized yet. Fixes both /context and the serve GET /session/:id/context-usage route at the source. Closes QwenLM#5763 Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
The TUI paints no background of its own and relies on the terminal's own background. Two elements broke that and rendered as off-colour blocks that could not be made consistent across terminals and themes: - The input box (QwenLM#5568) flood-filled theme.background.primary. Even when the theme's light/dark bucket matched the terminal (the QwenLM#5746 gate), the exact colour usually differed from the terminal's real background, so the prompt rendered as a distinct block — worst over SSH/remote where brightness detection is unreliable and defaults to dark. - The user-message half-line band (QwenLM#4595) painted a subtleBandColor band behind each user message, gated on the same theme/terminal match. Because history is rendered through Ink <Static> (committed rows are never repainted) and the gate only fires when the active theme matches the terminal, the band showed on some messages but not others across a theme switch — it cannot be made consistent. Stop painting both so the input area and user messages blend into the terminal background everywhere. User messages fall back to marginTop=1 for separation. The software cursor now derives its contrast from the terminal's detected brightness (getEffectiveTerminalBackground) so it stays visible with no fill painted. Also remove the band's now-unused helpers — subtleBandColor and supportsTrueColor (added by QwenLM#4595) and the dead UserMessageProps.width prop. Fixes QwenLM#5771. Generated with AI Co-authored-by: 秦奇 <[email protected]> Co-authored-by: Qwen-Coder <[email protected]>
…ences (QwenLM#5774) Bare `@<partial>` with no `<server>:` prefix now matches resource URIs and friendly names across all connected MCP servers, surfaced alongside the file results and injected as the canonical `@server:uri` reference — so a user can pull up a resource by a memorable fragment without first recalling which server exposes it. In the @-mention dropdown, resource rows keep the full `server:uri` reference intact on a single line, aligned, with the description column truncating instead — so resources that share a long URI prefix stay distinguishable.
* ci: collapse pr checks into ubuntu gate * ci: keep platform PR tests in matrix * ci: update no-ak gate wiring test * ci: bypass stale self-hosted git cache * ci: refresh self-hosted checkout cache * ci: refresh pr refs after cached checkout * ci: fetch fresh pr refs via github url * ci: retry stale pr merge checkout * ci: fetch stale pr merge ref directly * test: isolate qwen serve streaming home * test: keep qwen serve fake server off proxy * ci: check out PR head ref to avoid merge-ref lag The Ubuntu gate kept failing on the self-hosted runner: the PR checkout used github.ref (refs/pull/N/merge), which GitHub rebuilds asynchronously and can serve stale for minutes after a push, so the verify guard saw a tree without the PR head. The retry/refresh machinery added to work around this could not help — its direct GitHub fetch fallback times out on the self-hosted squid proxy. Check out refs/pull/N/head instead (immutable, published the instant the branch is pushed) for pull_request events in both the test and test_platforms jobs, and drop the 6-step retry/verify/refresh block. A single sanity guard stays to fail loud if the head is missing. Non-PR events keep github.ref; the merge queue validates the merged result.
Co-authored-by: ytahdn <[email protected]>
…age (QwenLM#5735) * docs: fix config/command/auth drift and surface model-providers page Audit docs/ against the current code and correct the highest-impact drift: - settings.md: move the mis-filed experimental.emitToolUseSummaries row into a new experimental section (cron/agentTeam/artifact/emitToolUseSummaries) and add general.language/outputLanguage/dynamicCommandTranslation and output.showTimestamps. - commands.md: document /cd, /history, /voice, /import-config and the /model --voice and /model <model-id> forms. - auth.md + model-providers.md: convert all modelProviders examples to the v5 { protocol, models } object shape, correct the /auth menu (Alibaba ModelStudio / Third-party Providers / Custom Provider), fix the default OpenAI model (qwen3.5-plus), document the vertex-ai auth type, mark envKey optional, and use kebab-case --openai-api-key/--openai-base-url flags. - overview.md + quickstart.md: rewrite the stale first-run auth flow; fix typo. - configuration/_meta.ts: surface the orphaned model-providers page in the nav. - qc-helper SKILL.md: add the 8 missing feature pages to the doc index. * docs: resolve review feedback — fix provider-name and ModelStudio casing Align docs with the code's provider labels and UI strings: - Z.ai -> Z.AI (presets/zai.ts: label 'Z.AI API Key') - iDeaLab -> Idealab (presets/idealab.ts: label 'Idealab API Key') - 'Model Studio' -> 'ModelStudio' (UI flowTitle 'Alibaba ModelStudio'; no 'Model Studio' in code) Applied across auth.md, overview.md, quickstart.md. Used --no-verify to avoid lint-staged reformatting pre-existing, unrelated (non-CI-enforced) table padding in auth.md; the five changed lines are individually prettier-clean. * docs: resolve review feedback — /history subcommands, language type, jsonc fence - commands.md: add missing '/history expand-on-resume' subcommand (historyCommand.ts registers collapse-on-resume, expand-on-resume, expand-now) - settings.md: general.language Type string -> enum (settingsSchema.ts declares type: 'enum') - model-providers.md: relabel the Example fence json -> jsonc (it contains // comments and two JSON docs) --no-verify: avoids lint-staged re-padding pre-existing, unrelated (non-CI-enforced) table columns; the three changed lines are content-only.
* docs: Align docs with current CLI behavior Update stale documentation and user-facing MCP OAuth guidance to match the current dialog-based flows, SDK permission semantics, current links, and Qwen OAuth status. Also replace Ink internal imports with public Ink APIs for the shared text input so the workspace builds against Ink 7. Co-authored-by: Qwen-Coder <[email protected]> * codex: address PR review feedback (QwenLM#5589) Co-authored-by: Qwen-Coder <[email protected]> * codex: address PR review feedback (QwenLM#5589) Co-authored-by: Qwen-Coder <[email protected]> * codex: address PR review feedback (QwenLM#5589) Co-authored-by: Qwen-Coder <[email protected]> * codex: address PR review feedback (QwenLM#5589) Co-authored-by: Qwen-Coder <[email protected]> * codex: address PR review feedback (QwenLM#5589) Co-authored-by: Qwen-Coder <[email protected]> * codex: address PR review feedback (QwenLM#5589) Co-authored-by: Qwen-Coder <[email protected]> * codex: address PR review feedback (QwenLM#5589) Co-authored-by: Qwen-Coder <[email protected]> * codex: address PR review feedback (QwenLM#5589) Co-authored-by: Qwen-Coder <[email protected]> * codex: address PR review feedback (QwenLM#5589) Co-authored-by: Qwen-Coder <[email protected]> * codex: address PR review feedback (QwenLM#5589) Co-authored-by: Qwen-Coder <[email protected]> * codex: fix BaseTextInput Ink import (QwenLM#5589) Co-authored-by: Qwen-Coder <[email protected]> * codex: address PR review feedback (QwenLM#5589) Co-authored-by: Qwen-Coder <[email protected]> * fix(core): surface MCP OAuth credential read failures Fix SSE OAuth credential pre-check failures by reporting token storage read errors before connecting. Update SDK coreTools docs and extension release link text from the follow-up review. Co-authored-by: Qwen-Coder <[email protected]> * fix(core): harden MCP OAuth error handling Handle stderr warning failures as best-effort and keep SSE 401 OAuth guidance when credential re-read fails. Co-authored-by: Qwen-Coder <[email protected]> * fix(core): keep SSE OAuth pre-read best effort Avoid blocking SSE MCP connections when the diagnostic credential pre-read fails, and cover BaseTextInput absolute-position edge cases. Co-authored-by: Qwen-Coder <[email protected]> * fix(core): handle SSE OAuth validation errors Co-authored-by: Qwen-Coder <[email protected]> * fix(core): surface MCP OAuth recovery guidance Co-authored-by: Qwen-Coder <[email protected]> * fix(core): cover MCP OAuth retry paths Co-authored-by: Qwen-Coder <[email protected]> * fix(core): address OAuth guidance review Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]>
* feat(cli): add workspace permissions rules API Co-authored-by: Qwen-Coder <[email protected]> * codex: fix CI failure on PR QwenLM#5743 Co-authored-by: Qwen-Coder <[email protected]> * fix(cli,sdk): address PR review comments on workspace permissions - normalizePermissionRules: skip malformed rules instead of rejecting the entire request, fixing read-modify-write bricking (review #1 & #4) - Add tests for addWorkspacePermissionRule/removeWorkspacePermissionRule covering the actual POST path (review #2) - Add JSDoc documenting non-atomic read-modify-write and TOCTOU risk on add/remove helpers (review #3) * fix(cli): wrap persist-fallback response in try/catch and add error path tests - Add try/catch around buildPermissionSettings in persist-fallback POST path, matching GET handler error handling - Add tests for ACP non-SessionNotFoundError, persistSetting failure, and unknown client id rejection * fix(cli): update acpAgent test for silent malformed rule dropping The normalizePermissionRules change to skip (instead of reject) malformed rules requires updating the acpAgent test to expect successful resolution with the malformed rule filtered out. * fix(cli): reject newly malformed permission rules Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): tighten workspace permission writes Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): harden workspace permission rules Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): handle ACP invalid params errors Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): pin workspace permission writes Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): report workspace permission write backend Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]>
…ols (QwenLM#5788) The TUI uses a consistent vocabulary of width-1 Unicode text glyphs (✦ ● ○ ◐ ✓ ↓ ↑ + ink-spinner). Two status indicators broke this with emoji, which render at width=2 and drift column alignment across the CI/LANG fallback branches. - THINKING_ICON: replace the three-way fallback (💡 / ⟡ / empty) with a single ∴ (U+2234 THEREFORE) constant. The math "therefore" sign matches the thinking block's "deriving a conclusion" semantics and sits on a stable wide base. Drops the isUtf8/CI branch entirely. - SummaryMessage success: replace ✅ with ✓ to match ToolMessage's existing success glyph — same "operation succeeded" meaning, no redundant emoji variant. Both replacements are width=1 and render identically across terminals/CI, removing the column drift the old fallback caused. Refs QwenLM#5787
…zard (QwenLM#5654) * fix(cli): restore saved custom model IDs when re-entering the auth wizard When re-entering the provider auth wizard for a ModelStudio provider that already had saved model IDs, the Model IDs step reset to the provider's built-in defaults instead of the user's saved models. Submitting then overwrote the previously saved custom model IDs with the defaults, losing the user's configuration. Share the existing model-lookup logic from core between the desktop/ACP path and the CLI wizard, and have the wizard pre-fill the Model IDs step with the saved models when present, falling back to the built-in defaults otherwise. Closes QwenLM#5636 Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): preserve user custom model IDs during provider update executeUpdate rebuilt the install plan with only built-in model IDs, so the prepend-and-remove-owned merge silently deleted user-added custom models. Carry custom IDs into the install plan so an update only refreshes built-in models and leaves user-configured ones untouched. --------- Co-authored-by: Qwen-Coder <[email protected]>
* fix(daemon): Reject stale prompt client admission Return invalid prompt client ids as synchronous admission failures so daemon clients do not wait forever after a stale client id is rejected. Co-authored-by: Qwen-Coder <[email protected]> * codex: address PR review feedback (QwenLM#5784) Log invalid prompt client admission rejections and document the fake bridge's synchronous admission contract. Co-authored-by: Qwen-Coder <[email protected]> * codex: address PR review feedback (QwenLM#5784) Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]>
…M#5755) * feat(serve): voice dictation over the daemon for the Web Shell Bring voice input to the `qwen serve` Web Shell. The browser captures the microphone, streams raw 16 kHz mono PCM to a new `/voice/stream` WebSocket, and the daemon transcribes server-side by reusing the existing CLI voice pipeline (realtime streaming + on-stop batch) — provider credentials never reach the browser, and it works whether the daemon is local or remote. Daemon: - `/voice/stream` WebSocket handler (serve/voice) reusing openVoiceStream / openQwenAsrRealtimeStream / transcribeVoiceAudio; resolves the workspace voiceModel from a ModelsConfig built off settings. - Routed through the existing ACP upgrade listener so it shares the loopback / host-allowlist / CSRF / bearer checks; concurrency-capped. - Advertises the `voice_transcribe` capability (unconditional, like auth_device_flow; the WS errors when no voice model is configured). - Relax `Permissions-Policy` to `microphone=(self)` so the same-origin shell can request the mic — an empty `microphone=()` allowlist blocked the prompt entirely. - Allowlist `voiceModel` on `/workspace/settings` so the picker can read it. Web Shell: - Mic button in the composer: click to record, click to stop -> the transcript is inserted into the input for review before sending. - `/model --voice` picker (sourced from `/workspace/providers`, since voice models are hidden from the session model list) and `/model --voice <id>`, persisted via the prompt channel like `/model --fast`. The voiceModel resolver now accepts a structural model lookup so the daemon can resolve it without constructing a full CLI Config. Co-authored-by: Qwen-Coder <[email protected]> * fix(serve): harden web voice streaming * fix(serve): clean stale voice capture resources * fix(serve): harden voice websocket errors * fix(serve): hide voice capability when token auth is configured * fix(serve): address voice review blockers * fix(web-shell): keep voice cancel available when disabled * fix(web-shell): inline the voice mic in the composer toolbar The mic was an absolute-positioned overlay anchored to the composer wrapper, so after the QwenLM#5775 chat-UI restructure it floated outside the input box (clipped at the bottom-right). Render it inside ChatEditor's `toolbarRight`, just before the send button, wiring the transcript insert to the composer core — it sits inline with the `/`, `@`, and send controls like the rest of the toolbar. Restyle to a Codex-style recording experience: an idle mic that matches the other toolbar icon buttons, and a recording pill with a live waveform (driven by the RMS meter), an elapsed `M:SS` timer, and a stop control. Also forward `/voice/stream` through the vite dev proxy (`ws: true`, scoped to the exact path so it doesn't shadow the client's own `client/voice/*` source modules) so the WebSocket reaches the daemon in dev instead of silently failing against vite. Co-authored-by: Qwen-Coder <[email protected]> * feat(serve): authenticate the voice WebSocket via a bearer subprotocol Voice was suppressed on any token-configured daemon because browsers cannot set an `Authorization` header on a WebSocket, so the bearer check on the shared ACP upgrade listener always failed for the browser mic. Let the browser carry the token in `Sec-WebSocket-Protocol` as `qwen-bearer.<base64url(token)>` (the only header a browser can set on a WS). The upgrade listener now accepts the token from either the `Authorization` header (non-browser clients, unchanged) or the subprotocol, hash-compared in constant time. `handleProtocols` selects a non-secret subprotocol (or none) so the token is never echoed back in the handshake response. This aligns the voice WS with how the ACP WS will authenticate browsers. With the WS now authenticated, drop the `!tokenConfigured` / `requireAuth` suppression of the `voice_transcribe` capability — it is advertised whenever the `/voice/stream` endpoint exists. Tests updated to assert voice is advertised under a token / `--require-auth`. Co-authored-by: Qwen-Coder <[email protected]> * fix(serve): make the voice WS subprotocol handshake complete + test it Review follow-up to the bearer-subprotocol auth. Fixes a real handshake bug and closes the test gap on the security-critical path. - Handshake bug: when the browser offered only the `qwen-bearer.*` subprotocol, the daemon's `handleProtocols` selected none, and a strict WS client (the `ws` library) aborts with "Server sent no subprotocol". The web-shell now offers a non-secret marker (`qwen-ws`) alongside the bearer one, so the daemon always selects the marker — never echoing the secret — and the handshake completes for strict and lenient clients alike. - Tests: add daemon-side coverage for the subprotocol auth path (accept valid token, reject wrong/malformed, no-token loopback) and assert the secret is never echoed back; add client coverage that the bearer token is offered as `[qwen-ws, qwen-bearer.<b64url>]` and round-trips. - Drop the unreachable try/catch around `Buffer.from(_, 'base64url')` (it never throws) and correct the comment. - a11y: mark the transcribing/interim regions as `role="status"` / `aria-live`, and add a `:focus-visible` ring to the idle mic. - Refresh the stale `useVoiceCapture` doc comment (token deployments are now supported in the browser) and fix the mock typing in VoiceButton.test.tsx. Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): ignore stale voice socket callbacks * test(serve): sync voice capability integration expectation * fix(serve): harden voice websocket review gaps * fix(serve): sanitize voice websocket failures * fix(serve): address voice review suggestions * fix(serve): harden voice websocket lifecycle --------- Co-authored-by: Qwen-Coder <[email protected]>
On macOS the Option key (⌥) maps to the meta keybinding, but the UI hint displayed "alt+t" which is confusing since Mac keyboards have no dedicated Alt key. Show the native ⌥ symbol on darwin so the on-screen hint matches the actual key users need to press.
Alex-ai-future
added a commit
that referenced
this pull request
Jul 1, 2026
- Reject inline prompt + scope flag combination with clear error (#1) - Add mutual exclusivity check for --project and --global (#5) - Verify setValue scope parameter in tests + add --global test (#2) - Extract scopeSuffix to shared variable, remove duplication (#3) - Remove dead i18n keys 'Select Model (this project)' / '(global)' (#4) - Fix scopeSuffix placement on model line not API key line (QwenLM#8) - Add fr.js / ja.js translations for scope keys (QwenLM#10) - Remove unused export ModelDialogPersistScope (#6) - Wrap non-interactive help text in t() with new flags (QwenLM#7) - Fix argumentHint grouping to show mode vs scope flags (QwenLM#11) Signed-off-by: Alex <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does
Changes the thinking block expand/collapse keyboard shortcut hint from "alt+t" to "⌥t" on macOS, so the on-screen hint matches the actual key users need to press.
Why it's needed
The keybinding is defined as
{ key: 't', meta: true }, which maps to the Option (⌥) key on Mac. However, the UI always displayed "alt+t to expand/collapse" — macOS keyboards have no dedicated Alt key, so Mac users never knew which key to press. This PR detectsprocess.platform === 'darwin'and shows the native ⌥ symbol instead.Reviewer Test Plan
How to verify
npm run devEvidence (Before & After)
Before:
(alt+t to expand)— confusing on Mac, no Alt key existsAfter:
(⌥t to expand)— matches the actual Option key on Mac keyboardsTested on
Risk & Scope
Linked Issues
N/A
中文说明
这个 PR 做了什么
在 macOS 上将思考块的展开/折叠快捷键提示从 "alt+t" 改为 "⌥t",使界面提示与实际需要按的键一致。
为什么需要
快捷键定义为
{ key: 't', meta: true },在 Mac 上对应 Option (⌥) 键。但界面始终显示 "alt+t",而 Mac 键盘没有独立的 Alt 键,导致 Mac 用户不知道按哪个键。此 PR 检测process.platform === 'darwin'并显示原生的 ⌥ 符号。审查者验证方案
如何验证
npm run dev证据(Before & After)
Before:
(alt+t to expand)— Mac 上令人困惑,不存在 Alt 键After:
(⌥t to expand)— 与 Mac 键盘上的 Option 键匹配风险与范围