Commit graph

49 commits

Author SHA1 Message Date
jinye
b3873571aa
fix(daemon): harden Todo Stop Guard continuations (#7821)
* fix(daemon): harden Todo Stop Guard continuations

Linearize Guard continuation ownership across bridge consumers, preserve queued input across failure paths, and tighten session lifecycle and lineage handling.

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

* fix(daemon): yield to event loop in waitForActiveTurnsToSettle (#7821)

* fix(channels): discard retired ACP sessions

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

* fix(cli): preserve superseded continuation state

Preserve unsent continuation results across prompt supersession and cancellation, and keep closing sessions from restoring stale FIFO priority.

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-07-28 06:51:40 +00:00
chinesepowered
3a85d48e77
fix(desktop): scale formatBytes past GB so terabyte sizes don't render as "undefined" (#7623)
formatBytes only had B/KB/MB/GB units and indexed the array with an
unclamped log-based exponent, so any value >= 1 TB overflowed to
"1.0 undefined". This is reachable in api-tools: the "Response too
large" guard formats an untrusted remote Content-Length, which a
server can report in the terabytes.

Extend the unit table through EB, clamp the exponent to the last unit,
and guard non-finite / sub-1 inputs so the function never emits
"undefined" or "NaN". Add formatBytes coverage to the existing
binary-detection test (range/limit guards plus the TB/PB/EB and
non-finite cases that fail on the old code).
2026-07-25 09:21:15 +00:00
Dragon
1fec09423b
fix: support context-inheriting subagents in headless mode (#7378)
* fix(core): support headless fork subagents

* fix(desktop): avoid false background agent events

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-21 12:33:13 +00:00
jinye
276094ae67
fix(core): Fence concurrent ACP session writers (#7237)
* fix(core): fence concurrent ACP session writers

Add an ACP-only cross-process writer lease, authoritative reload, append fencing, turn admission, and acknowledged close semantics for persisted sessions.

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

* fix(core): add session writer diagnostics

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

* fix: address session writer review feedback

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

* fix: preserve session cleanup failures

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

* test(core): restore legacy recorder coverage (#7237)

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

* fix(cli): bound live session close gates

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

* fix(serve): isolate slow session close

Keep reclaim cleanup failures from rolling back an acquired writer lock, and let a slow per-session close finish without reaping multiplexed sibling sessions.

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-21 07:36:07 +00:00
Dragon
d4db5fcfab
feat(core): improve subagent delegation defaults and guardrails (#7048)
* docs(design): define default background subagents

* feat(core): improve subagent delegation defaults

* docs(core): cross-reference the three background-classification sites

Add pointer comments linking the core dispatch source of truth
(AgentTool.execute) and its two UI mirrors (web-shell
isBackgroundSubAgentToolCall, desktop detectBackgroundEvents) so the
replicated top-level-agent background heuristic is not changed in
isolation. Addresses PR review feedback.

* fix(core): align background classification for fork and named-teammate launches

Address review feedback on the background-classification rule so core dispatch
and the two UI classifiers stay consistent:

- core: exclude a name-without-active-team launch from the default-background
  path so it stays foreground, matching both UI classifiers (which exclude
  name). Previously such a launch was backgrounded by core but tracked as
  foreground by the UIs.
- web-shell and desktop classifiers: exclude subagent_type "fork" from the
  default-background heuristic, mirroring core's !isForkRequested guard. A
  top-level fork request with an omitted flag runs foreground in core but was
  classified as background by the UIs.
- add a core dispatch test asserting a working_dir launch with an omitted
  run_in_background flag stays in the foreground.

* test: cover fork/background classification and precedence per review feedback

Address unresolved review threads on PR #7048:
- Add web-shell and desktop UI classifier tests asserting an omitted-flag
  `subagent_type: "fork"` launch stays in the foreground, verifying the
  documented `!isForkRequested` parity with core dispatch.
- Add a core AgentTool test asserting an explicit `run_in_background: false`
  overrides a subagent config with `background: true`, locking in the
  `run_in_background ?? config` precedence against a `||` regression.
- Harden the Explore read-only prompt: pipelines must not send data to a
  network endpoint (no curl/wget/nc), closing the `cat file | curl`
  exfiltration gap.

* fix(core): restore general no-unnecessary-files guard in general-purpose prompt

Address review feedback: the rewritten general-purpose prompt dropped the
broad guard against creating unrequested files, keeping only the
documentation-specific line. Restore a general 'do not create files unless
necessary' guard so speculative utility/config files are not created.

* test(desktop): cover named-teammate foreground guard in detectBackgroundEvents

Add a desktop tool-matching test asserting a top-level Agent with a
`name` set (named teammate) stays foreground and emits no
task_backgrounded event, mirroring the web-shell classifier's
named-teammate coverage and the existing fork-exclusion test.

* test(core): cover named-teammate foreground dispatch when flag omitted

Add a core-dispatch test asserting a top-level Agent launch with `name`
set and `run_in_background` omitted stays foreground when no team is
active, guarding the `this.params.name === undefined` exclusion in
backgroundRequested directly (previously only covered by the UI
classifiers).

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-18 08:52:48 +00:00
Matt Van Horn
91c8b6f220
fix: address self-review on desktop MCP permission lifecycle (#7013)
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-18 08:49:05 +00:00
jinye
c538bd70d2
feat(core): emit liveness heartbeats for silent foreground shell commands (#6876)
* feat(core): emit liveness heartbeats for silent foreground shell commands

Silent foreground commands previously produced no events between spawn
and settle, so ACP gateways and stream-json consumers could not tell a
long-running command from a dead session. The shell tool now emits a
structured ShellProgressData through the existing updateOutput channel
whenever no display update has fired for tools.shell.heartbeatIntervalMs
(default 10s, 0 disables). Heartbeats carry liveness stats only - never
command output - and never enter model context.

Consumers: the ACP session forwards heartbeats as meta-only
tool_call_update frames (gated so a tick racing the settle path cannot
regress status after completion) and records heartbeat span attributes;
stream-json forwards them as tool_progress events behind
includePartialMessages; the TUI scheduler, React hook, and subagent
runtime ignore them so live output views are not replaced by stats
objects.

* docs(design): add silent command heartbeat design doc

* fix(acp): keep tool_call_update heartbeats from breaking in-repo consumers

Codex review of the heartbeat change found that in-repo ACP consumers
did not tolerate the new meta-only in_progress frames. A full sweep of
tool_call_update consumers found three that mishandled them, each now
guarded with a regression test:

- The desktop agent converted every tool_call_update into a terminal
  tool_result, so the first heartbeat would prematurely complete the
  command with an empty result. It now skips in_progress updates.
- DaemonChannelBridge requires kind on tool_call_update and flagged the
  kind-less heartbeat as a malformed-protocol error every interval. It
  now drops kind-less in_progress frames silently.
- The web-shell daemon UI normalizer derived the tool block title from
  _meta.toolName, overwriting the human-readable title on every
  heartbeat. It now drops heartbeat frames outright.

The remaining consumers (VS Code companion, acp-bridge compaction,
session export, daemon TUI adapter) merge updates conditionally and are
heartbeat-safe without changes.

* fix(core): address PR review — heartbeat monotonic gate, guard scope, telemetry

Review round 1 on #6876 (yiliang114, wenshao, chiga0, qwen3.7-max):

- shell.ts: the silent-idle gate now uses the monotonic performance.now()
  clock (via lastOutputPerfTime, falling back to spawn time) instead of the
  Date.now()-based lastUpdateTime, so an NTP step can neither skew the
  payload nor misfire a heartbeat — matching the design doc's monotonic
  commitment. It also keys off actual output arrival rather than the
  throttled display update.
- session-tracing.ts: endToolExecutionSpan now applies caller-supplied
  attributes BEFORE the canonical keys (duration_ms, success, error) so a
  passthrough attribute can never mask the span's own outcome fields.
- desktop qwen-agent.ts: the in_progress drop guard is now scoped to frames
  carrying _meta.shellProgress, matching the daemon bridge and web-shell
  normalizer guards, so a future non-heartbeat in_progress frame is not
  silently swallowed.
- Tests: the desktop regression test now pins result==='done' (previously
  it stayed green even with the guard removed); added a Session.test
  assertion that heartbeat counts reach the tool-execution span attributes.

* fix(acp): align desktop heartbeat guard with normalizer; test kind pass-through

Review round 2 on #6876 (qwen3.7-max via ci-bot):

- The desktop qwen-agent in_progress drop guard was broader than the
  web-shell normalizer's: it dropped any in_progress + shellProgress frame
  regardless of kind, while the normalizer only drops kind-less ones. The
  comment claimed they matched. Added the kind-absent check so the desktop
  guard matches the normalizer exactly — a kind-bearing frame now passes
  through on both platforms (heartbeats emitted by the ACP session never
  carry a kind, so real behavior is unchanged).
- Added pass-through tests on both sides (daemonUi + desktop) asserting an
  in_progress frame WITH a kind normalizes to a tool.update / tool_result
  rather than being dropped, so the load-bearing kind-absent condition is
  no longer only exercised on the drop path.

* fix(channels): scope daemon bridge heartbeat drop to shellProgress frames

Review round 3 on #6876 (qwen3.7-max via ci-bot): the DaemonChannelBridge
heartbeat guard lived in the shared tool_call / tool_call_update case and
dropped ANY kind-less in_progress frame, so a genuinely malformed kind-less
tool_call (status in_progress, no shellProgress) was silently swallowed
instead of reaching emitProtocolError. Gate the drop on _meta.shellProgress
— matching the qwen-agent and web-shell normalizer guards — so real
heartbeats are still dropped while malformed frames are flagged. Added a
regression test for the malformed path.
2026-07-15 00:07:26 +00:00
qwen-code-ci-bot
dd8bdf8e2e
chore(release): desktop desktop-v0.0.5 (#6795)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-14 05:01:40 +00:00
VectorPeak
28bfadfded
fix(desktop): preserve MCP URL query suffixes (#6587)
* fix(desktop): preserve MCP URL query suffixes

Normalize MCP source URLs by trimming trailing slashes only from the path portion before query or fragment components. This keeps query parameters and fragments intact when they legitimately end with a slash.

Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>

* docs(desktop): clarify MCP URL normalization scope

Clarify that MCP URL normalization removes trailing slashes only from the path portion while preserving query strings and fragments unchanged.

Co-authored-by: qwen-code-ci-bot <253268222+qwen-code-ci-bot@users.noreply.github.com>

Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>

---------

Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
2026-07-11 10:16:51 +00:00
VectorPeak
47f62a466c
fix(desktop): preserve glued automation history records (#6344)
* fix(desktop): preserve glued automation history records

* fix(desktop): refine automation history recovery

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* test(desktop): cover automation history rewrite normalization

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix(desktop): preserve recovered history before stray brace

* test(desktop): assert history rewrite after stray suffix

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-05 23:53:49 +00:00
Gaurav
06fc052719
fix(desktop): enforce transform_data isolation (#6285)
* fix(desktop): enforce transform_data isolation

* fix(desktop): tighten transform_data isolation

* fix(desktop): harden sandbox isolation followups
2026-07-05 05:46:53 +00:00
pomelo
e92fcbab46
fix(cli): switch TUI prefix ✦→◆ to fix glyph overflow on some terminals (#5974)
* fix(cli): replace ✦ (U+2726) with ◆ (U+25C6) and add ∵/∴ thinking icons

- Replace ✦ with ◆ across all TUI components to fix East Asian
  Ambiguous width misalignment (string-width reports 1 but terminals
  render 2 columns).
- Use ∵ (because) during thinking streaming, ∴ (therefore) when
  thinking is complete — matches the mathematical reasoning pair.

Co-Authored-By: Qwen Code <noreply@alibaba-inc.com>

* fix(cli): reduce STATUS_INDICATOR_WIDTH from 3 to 2 after ◆ replacement

◆ (U+25C6) is a consistent width-1 character across all terminals,
so the tool status indicator no longer needs the extra column that
was reserved for the ambiguous-width ✦ (U+2726).

Co-Authored-By: Qwen Code <noreply@alibaba-inc.com>

* fix(cli): catch missed ✦→◆ references in tests, docs, and scenarios

* fix(cli): shorten tmux spinner frames from 3 to 2 chars to match STATUS_INDICATOR_WIDTH=2

TMUX_SPINNER_FRAMES changed from ['.  ', '.. ', '...'] to ['· ', '··']
to prevent 1-column overflow in tmux when STATUS_INDICATOR_WIDTH was
reduced from 3 to 2 after the ◆ replacement.

* revert(cli): keep narrow '.' tmux spinner frames instead of ambiguous '·'

'.' (U+002E) is Narrow (always 1 col), giving a guaranteed fixed-width
tmux spinner. '·' (U+00B7) is East Asian Ambiguous, so on ambiguous-width=2
terminals the frames become 3/4 cols and the spinner jitters — the opposite
of the "fixed-width frames" the surrounding comment promises.

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

---------

Co-authored-by: Qwen Code <noreply@alibaba-inc.com>
Co-authored-by: pomelo.lcw <pomelo.lcw@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-30 22:44:34 +00:00
qqqys
a1bcda6c1b
feat(desktop): voice dictation in the desktop app (#5856)
* feat(desktop): voice dictation in the desktop app

Bring the `/voice` dictation feature to the desktop app (#5796), matching the
Web Shell (#5755). The renderer captures the microphone and streams raw 16 kHz
mono PCM to a loopback `/voice/stream` WebSocket in the Electron main process;
the server transcribes by reusing the CLI voice pipeline (batch + realtime) with
the qwen credentials, so provider keys never reach the renderer.

- server-core/voice: a standalone loopback WS server (separate from the RPC
  server, shares its token via the `?token=` query) + ported batch/realtime
  transcription pipeline + SSRF guard.
- Credentials resolve from the qwen CLI config: OAuth (`~/.qwen/oauth_creds.json`)
  → API key (`~/.qwen/settings.json` DashScope provider) → env.
- Renderer: a mic in the composer toolbar that swaps to a recording bar
  (waveform + timer) while dictating; transcript is inserted for review.
- Voice on/off and model are configurable in Settings → Input and the composer
  model dropdown, persisted via a `voiceEnabled` / `voiceModel` setting.

Co-authored-by: Qwen-Coder <noreply@qwen.ai>

* fix(desktop): harden voice dictation review gaps

* test(desktop): update voice ipc channel inventory

* fix(desktop): hide voice stream internals from errors

* test(desktop): cover voice websocket paths

* test(desktop): cover voice transcription paths

* fix(desktop): detect voice capture frame drops

* fix(desktop): address voice review blockers

* fix(desktop): address voice security blockers

* fix(desktop): address voice review blockers

* fix(desktop): address voice review blockers

* fix(desktop): address voice shutdown review

* fix(desktop): address voice review blockers

* fix(desktop): fix server-core voice typecheck and harden config resolution

Fix three type errors that left server-core's typecheck red:
- type the readQwenJson test mocks generically (`<T,>() => ... as T | undefined`)
  to match the real `<T>(file) => Promise<T | undefined>` signature
- replace `HeadersInit` (undefined under lib ESNext/no DOM) with
  `RequestInit['headers']`
- widen the fetch stub cast to `as unknown as typeof fetch` (the stub lacks
  `preconnect`)

Also apply small hardening to resolve-voice-config:
- normalizeBaseUrl strips userinfo so a user:pass@host base URL can't leak
  credentials into outbound requests or errors
- OPENAI_API_KEY without OPENAI_BASE_URL now throws a specific message instead
  of a generic no-credentials error
- readQwenJsonFromDisk honors QWEN_HOME for the config base dir

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(desktop): address voice review follow-ups (logging, dedupe, truncation)

- voice-ws-handler: tag connection logs with a short per-connection id so
  concurrent sessions are distinguishable; log unrecognized text frames; add
  bufferedBytes/pcmChunks context to the finalize "no session/audio" warnings.
- voice-frame-sender: fire onTooManyDroppedFrames once as the threshold is
  first crossed instead of on every later dropped frame.
- qwen-asr-realtime-session: rename `failed` -> `settled` for parity with
  voice-stream-session (the flag is set on both fail and the success path).
- voice-stream-session / qwen-asr-realtime-session: drop the redundant outer
  slice in formatServerErrorMessage so sanitize's truncation `...` indicator
  survives (credential redaction unchanged).
- voice-server: drop the dead `if (!isEnabled())` wrapper that the early
  return above already makes unconditional.
- voice-stream-session test: cover the result-generated sentence_end commit
  branch and the lastPartial reset (multi-sentence path).

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(desktop): normalize QWEN_HOME and scope voice pending-op cap to control frames

Voice config resolution read the raw QWEN_HOME env value, so QWEN_HOME=~/x or
a relative value pointed desktop voice at a different directory than the rest
of Qwen (which normalizes via core's Storage.getGlobalQwenDir) and missed the
OAuth/settings credentials. Mirror core's normalization in a local helper:
expand a leading ~, resolve a relative path to absolute, and fall back to
~/.qwen when QWEN_HOME is unset/empty.

Also scope the per-connection pending-operation cap to control frames. PCM
frames buffered behind a slow upstream connect (up to the 8s connect timeout)
no longer inflate the count cap and falsely trip "Too many pending voice
messages." — buffered audio is already bounded by queuedBytes. The drain gate
(pendingOperations) still counts every message, so close/slot-release
semantics are unchanged.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(desktop): reject credentialed voice base URLs; scope batch byte cap to batch

Address qwen-code-ci-bot review on the desktop voice ASR handler.

- resolve-voice-config: normalizeBaseUrl now throws on embedded userinfo
  instead of stripping it. For `https://real-host@evil.com/...` the URL
  parser already resolves `evil.com` as the host, so clearing userinfo
  afterward would silently proceed with an attacker-controlled host. A
  legitimate voice base URL never carries credentials.
- voice-ws-handler: MAX_AUDIO_BYTES (~5.5-min / 10 MB) is the batch WAV/file
  cap. Streaming forwards frames immediately and is bounded by
  MAX_CONNECTION_MS (the 6-min hard timer) + queuedBytes, so counting
  already-forwarded frames toward the batch cap cut legit streams off ~30 s
  early. Enforce the file cap for batch sessions only; the batch 10 MB cap
  is unchanged.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(desktop): redact voice stream errors; drop unused qwen:// origin; re-fire voice URL retry

Address voice review on 8b19cffcf:

- Streaming session errors reached the renderer raw: the onError callback and
  the streaming open/finish paths forwarded errMessage(error) straight to
  fail(), bypassing the credential redaction the batch path applies. An
  upstream ws.on('error') can surface auth URLs / Bearer tokens, so route
  streaming error text through the same sanitizeResponseDetails redactor
  (passing the apiKey) before it leaves the server. Batch errors are already
  sanitized inside transcribeQwenAsrBatch and stay untouched.

- Remove the qwen:// scheme from the voice origin allowlist. No custom app
  scheme is registered anywhere (only craftagents deep-links and the
  thumbnail:// privileged scheme), so accepting it only let an unregistered
  same-machine scheme pass origin validation — tightened as defense-in-depth.

- Fix the voice URL cold-start retry: setWsUrl(null) is a no-op when the value
  stays null, so the [wsUrl] effect never re-fired and voice stayed unavailable
  on slow cold starts (OAuth refresh / slow disk > 1.5s). Add a bounded retry
  counter to the effect deps so it re-polls until the URL resolves.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(desktop): address voice PR #5856 review feedback

Cleanups:
- voice-stream-session: set settled in the close-handler else branch so a late
  error/close cannot re-enter and double-fire onError.
- qwen-asr-realtime-session: extract the duplicated "connection closed
  unexpectedly" message into a single module constant.
- shared/storage: inline the no-op loadStoredConfigForVoiceSettings wrapper.

Tests:
- voice-stream-retry: cover openVoiceStreamWithRetry (first-try success,
  retry-then-success, non-retryable rethrow, give-up) using fake timers.
- voice-server: extract pure classifyVoiceUpgrade for the 4 upgrade guards;
  unit-test the exact statuses and add an integration test asserting each
  guard rejects without upgrading.
- net-guard: add a DNS-lookup-failure test for assertVoiceBaseUrlNetworkAllowed.

Fix:
- useVoiceCapture: treat a graceful 1000 close as normal completion instead of
  surfacing it as a connection error.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(desktop): order voice upgrade token check before disk read; settle realtime close

Address two voice PR #5856 review notes:

- classifyVoiceUpgrade: check the voice token before the disk-reading
  isEnabled guard, so an upgrade with a wrong/missing token is rejected
  without a loadStoredConfig() disk read (a pre-auth wasted-work surface).
  New guard order: path -> token -> disabled -> origin.
- qwen-asr-realtime-session: set settled=true in the ws 'close' handler's
  terminal branches, mirroring voice-stream-session, so a late error/close
  event can't re-enter via fail() and double-fire reject/onError.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(desktop): harden voice backpressure visibility + IPC sender guard (PR #5856)

Criticals (wenshao):
- voice-stream-session: count dropped audio frames/bytes under upstream
  (DashScope) backpressure and log the cumulative total on entry and on
  recovery. Previously frames were dropped silently, leaving invisible
  transcript gaps with no log/counter.
- electron main: validate the sender of `__get-voice-stream-url`. The URL
  embeds the loopback auth token, so only the app's own top-level renderer
  frame (file:// when packaged, the Vite dev origin in dev) may read it;
  injected/cross-origin sub-frames and stray webviews get null.

Suggestions (wenshao):
- InputSettingsPage: revert the optimistic voice enabled/model state when the
  IPC write rejects, so the UI never lies about the persisted value.
- useVoiceDictation: tick the elapsed timer at 1s instead of 100ms — the bar
  floors elapsed to whole seconds, so finer ticks were ~10x redundant renders.
- voiceModels: add `description` as the single source of truth and consume it
  in both the settings and composer pickers (drops duplicated computation).
- voice-server: gracefully WS-close clients before force-terminating on
  shutdown, so an in-flight transcript can flush instead of being dropped by a
  TCP reset; bounded by a short grace period and an absolute deadline.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(desktop): await + rollback voice model change on IPC failure

handleVoiceModelChange in FreeFormInput fired the setVoiceModel IPC and
discarded the promise (void). If that IPC rejected (handler throws,
settings file locked), the renderer kept the optimistically-selected
model while the persisted value stayed old. The voice server reads the
persisted model per recording (resolveDesktopVoiceConfig), so the
dropdown and the actual ASR model silently diverged.

Await the IPC and roll back the optimistic setVoiceModel on failure,
matching the established pattern in InputSettingsPage. Failures are
reported through the existing logInputError helper.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(desktop): report voice backpressure totals at session end; log graceful shutdown

Address ci-bot review on the desktop voice PR.

- voice-stream-session: surface the cumulative dropped-frame total exactly once
  when a session ends. droppedFrames/droppedBytes were warned only on
  backpressure enter/recover transitions, so a session that ended while still
  dropping (or after a recovery) lost the running total. A one-shot
  reportDroppedTotals() now fires from every terminal path (fail, task-finished,
  unexpected close), logging only when frames were actually dropped. Adds tests
  that a dropping session logs the total once and a clean session logs nothing
  (mutation-checked: disabling the report fails the test).

- voice-server: closeVoiceServerResources was silent during graceful shutdown,
  unlike the disabled-close path. Thread the existing Logger in and add concise
  logs matching the existing style: shutdown start with active client count,
  force-terminate of stragglers after the grace period, and shutdown complete.
  terminateVoiceClients now returns the count it terminated so the
  force-terminate log is precise. Adds a test asserting all three lines.

The reviewer's mock.module ordering concern for the backpressure test was
assessed and found non-fragile: the test registers the platform mock and then
dynamically imports the module under test (await import after mock.module), so
bun re-instantiates voice-stream-session.ts with the mock active regardless of
load order. Verified across isolation, the full voice suite, and orderings that
pre-load the module with the real logger first; all green. No refactor made.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* test(desktop): cover voice dropped-totals fail/close paths; clarify recovery-log comment

reportDroppedTotals() runs from three terminal paths but only the
task-finished path was tested. Add deterministic tests for the two
untested paths and lock the idempotency guard:

- fail() path: an upstream socket error drives fail() (which sets
  `settled` before reporting); assert the totals line logs exactly once.
- ws.on('close') path: a mid-session close (which reports before setting
  `settled`, the inverse order of fail()); assert it logs exactly once.
- idempotency: a close followed by a late task-finished invokes
  reportDroppedTotals() twice — task-finished is not short-circuited by
  `settled` — so only the droppedTotalsReported guard keeps the line to a
  single entry. Mutation-verified: removing the guard double-logs;
  disabling the fail()-path report drops the fail() test's log.

Also reword the backpressure-recovery comment: droppedFrames/droppedBytes
are cumulative session counters (never reset between episodes), so the
recovery log reports the running total, not a per-episode count.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(desktop): log disabled-path straggler terminations; drop redundant default args

On the voice stream server, the disabled-grace close path force-terminated
straggling clients with no diagnostic, unlike the shutdown path which logs
the force-terminated count. Add a parity warn via a small exported
terminateDisabledVoiceClients() helper (a clean unit-test seam, matching the
existing exported close/terminate helpers) and cover it with a deterministic
test.

Also stop hard-coding CLOSE_TIMEOUT_MS / SHUTDOWN_GRACE_MS at the
closeVoiceServerResources() call site: they were only the function's own
defaults, forwarded just to reach the log param, and would silently drift if
the defaults changed. Pass undefined so the defaults apply.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(desktop): count realtime ASR dropped frames; cover frame-trust gate; sanitize status

- voice(server-core): realtime ASR session counts dropped frames/bytes under
  socket backpressure and logs the cumulative total once at session end
  (parity with voice-stream-session reportDroppedTotals; idempotent across the
  fail/session.finished/close terminal paths). Logger is injectable via deps so
  the drop totals are unit-tested deterministically.
- voice(electron): surface voice-server URL retry exhaustion instead of silently
  disabling dictation — one-shot console.warn after the retry budget plus a new
  initFailed flag on the hook return. Retry timing/behavior unchanged.
- security(electron): extract isTrustedRendererFrameUrl (the sole guard gating
  the voice token) into main/voice/frame-trust.ts and unit-test file://, dev
  origin, cross-origin, undefined, malformed, and the VITE_DEV_SERVER_URL
  fallback. Behavior preserved; index.ts imports the helper.
- voice(server-core): sanitize response.statusText with sanitizeResponseDetails
  like the body, so a hostile ASR proxy can't leak secrets via the status line.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

---------

Co-authored-by: Qwen-Coder <noreply@qwen.ai>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-28 15:47:51 +00:00
VectorPeak
877e8ab777
fix(desktop): normalize source slug validation errors (#5911)
* fix(desktop): normalize source slug validation errors

* fix(desktop): address source slug review feedback

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-06-28 04:53:41 +00:00
qqqys
673454839e
feat(memory): add a git-shared team memory tier (#5886)
* feat(memory): add a git-shared team memory tier

Add an opt-in third auto-memory tier — TEAM — stored in the repository at
`.qwen/team-memory/` and shared with collaborators through git. The private
USER and PROJECT tiers are unchanged.

- Enabled via the `memory.enableTeamMemory` setting (default off) or the
  `QWEN_CODE_MEMORY_TEAM` env override; inactive otherwise.
- The model is taught a three-tier prompt and when to route saves to the
  shared tier (project-wide conventions, team-wide references).
- Writes to the team directory are scanned for secrets (a curated
  gitleaks-style ruleset) and hard-blocked, since the directory is committed
  to the repo and visible to everyone with repo access.
- Team writes default to 'ask' permission — not auto-allowed like the private
  tiers — so they are proposed for confirmation in the default approval mode
  and surface in the git diff for review.

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

* fix(vscode): update settings schema for team memory

* fix(memory): harden team memory review gaps

* fix(memory): address team-memory PR review feedback

- secret-scanner: AWS key suffix is base62 ([A-Z0-9]), not base32
  ([A-Z2-7]) — old class missed IDs with digits 0/1/8/9; add a
  positive test for such a key.
- paths: document that isAnyAutoMemPath deliberately excludes team
  memory (security-load-bearing — team writes must stay 'ask').
- extractionAgentPlanner: name the team-memory dir in the scoped deny
  message so a denied team write is debuggable.
- prompt: spell the tier count via a number-word lookup so a future
  4th tier never silently reads "two".
- team-memory-secret-guard: debug-log matched rule IDs only (never the
  secret content) when a write is blocked.
- edit: when a blocked team write's secret already exists in the
  on-disk file, tell the user to delete the committed secret.
- docs: caveat that a directory-form gitignore (.qwen/) makes the
  !-reinclude a no-op; use the file-glob form.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): harden team-memory secret guard on blocked writes and notebooks

- write-file: blocked team-memory secret write now returns an `error`
  field (INVALID_TOOL_PARAMS) so the framework treats it as a failure
  instead of a silent success, mirroring edit.ts.
- notebook-edit: scan the serialized .ipynb that hits disk through the
  team-memory secret guard before writing, blocking with the same
  `error` field; behavior unchanged for non-team-memory paths.
- store: log non-ENOENT errors in readTeamAutoMemoryIndex before
  re-throwing, so EACCES/EIO/ELOOP no longer vanish via config.ts's
  best-effort `.catch(() => null)`.
- config: emit a debug diagnostic when team memory is enabled but
  suppressed by the untrusted-workspace trust gate.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* feat(memory): auto-generate the team memory index

Generate the team `MEMORY.md` index by scanning the saved memory files
instead of having the model hand-maintain it. The index is derived and
ordered by path, so the committed file is deterministic across machines.
This removes the git merge-conflict surface a hand-edited shared index would
have, keeps the index consistent with the files, and frees the model from the
two-step save for the team tier.

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

* feat(memory): opt-in git sync for team memory

Add an opt-in, best-effort git sync of team memory at session start so a
daemon can share team memory through the remote without manual git. Off by
default; enabled with `QWEN_CODE_MEMORY_TEAM_SYNC=1`.

It commits the team directory (only that path), pulls fast-forward-only, then
pushes. `--ff-only` is deliberate: it never creates a merge commit or a
conflict — a diverged branch is skipped rather than touching the working tree.
All git is best-effort and never throws, so a failure cannot break a session.
Commits can be attributed to the acting user via an optional author, for
cooperative per-user attribution on a shared daemon.

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

* fix(core): close team-memory secret-guard regex + test gaps

- secret-scanner: add `_` to the two T3BlbkFJ-marker char classes so legacy
  base64url OpenAI keys with underscores in the body no longer bypass the
  scanner; extend the OpenAI positive samples with an underscore key body.
- edit: cover the pre-existing-secret branch — seed the on-disk file with a
  full detectable token, assert the distinct "already exists" message and that
  the blocked edit leaves the file untouched.
- team-paths: cover the first-ever write before .qwen/team-memory exists,
  exercising realpathNearestExisting walking up to an existing ancestor.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* feat(core): add Google OAuth client secret and SendGrid secret-scanner rules

Extend the team-memory secret scanner with two more high-confidence,
distinctive-prefix credential patterns (both gitleaks rules):

- gcp-oauth-client-secret: GOCSPX- prefix + 24 base64url chars,
  placed next to the existing GCP AIza API-key rule.
- sendgrid-api-token: SG. + 22-char id + . + 43-char secret.

Both have a near-zero false-positive bar, matching the curated set's
existing criteria. Add a load-bearing positive sample per rule.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): warn when team memory is enabled but not git-shareable

When the team tier is active, emit a one-time startup warning if its
directory is git-ignored (e.g. a directory-form `.qwen/` ignore that a
`!`-reinclude cannot escape) or there is no git root — so the tier never
silently shares nothing. Gated on the active (trusted + enabled) tier;
probes a representative file path with `git check-ignore`, and is
latched so refreshHierarchicalMemory re-runs do not re-emit it.

Closes the last open review thread (wenshao + qwen-code-ci-bot both
flagged the silent-inert case); the docs caveat already shipped.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* feat(memory): dedup team index by description; fix empty-frontmatter parse

The team index now collapses entries that share a (normalized) description
into one line — listing the other files via "(also: …)" — so two people
saving the same shared fact don't surface as duplicates. Nothing is dropped;
every file path is preserved and the grouping is deterministic (input is
path-sorted).

Also fix a pre-existing frontmatter bug: parseFrontmatterValue used `\s*`,
which crosses newlines, so an empty value (`description:`) captured the next
frontmatter line. Bound it to horizontal whitespace.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* feat(memory): add memory.enableTeamMemorySync setting

Team-memory git sync was env-only (QWEN_CODE_MEMORY_TEAM_SYNC). Add a
first-class `memory.enableTeamMemorySync` setting mirroring the
enableTeamMemory plumbing (settingsSchema, cli/core config, acpAgent
type/schema/default/keys/normalizer, desktop mirror), with the env var kept
as a 0/1 override. Off by default. Docs updated.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): audit hardening — resilient scan, deterministic team index

From a multi-round adversarial audit of the team-memory tier:

- scan: a single unreadable memory file (EACCES, or a TOCTOU delete during
  `git pull`) no longer rejects the whole scan and wipe every memory from the
  index — per-file failures are caught and skipped (debug-logged).
- indexer: order the team index by code unit, not `localeCompare()`, so the
  committed MEMORY.md is byte-identical across machines/locales and the
  ff-only sync can't wedge on index churn.
- scan: escape the frontmatter key before building its RegExp (latent hardening).
- config: surface a skipped/failed team-memory sync at debug level instead of
  silently swallowing it.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): deterministic team index cap + surface diverged sync

Round-2 audit follow-ups:

- scan: the >200-file cap selected its subset by mtime + locale basename, which
  differs per machine/locale — so a large team's committed index churned and
  wedged the ff-only sync. Team scans now cap by code-unit path (deterministic);
  private tiers keep mtime-recency (never shared, so it's fine).
- sync: a diverged branch made `pull --ff-only` (and the push) fail with no
  `skippedReason` — a silent no-op for a user who opted into sync. Now reported
  as `pull-failed` / `push-failed` and logged. Docs note pull/push are
  whole-branch, not path-scoped.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): regenerate settings schema for enableTeamMemorySync

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): harden team-memory git sync (reconcile-first, scoped push, safe failure)

The opt-in team-memory git sync could publish unrelated local commits, wedge a
two-writer branch, leave team files staged on commit failure, and hang session
start on a credential prompt. Harden all four maintainer-flagged criticals:

- Reconcile first: run `git pull --ff-only` BEFORE committing so the local team
  commit lands on top of upstream instead of diverging; a diverged branch is
  skipped cleanly as `pull-failed` rather than committing then wedging `--ff-only`.
- Scoped push: push only when THIS sync created the commit and the branch was
  not already ahead of `@{u}` (pre-sync), via an explicit single-branch refspec
  (`HEAD:<merge-ref>`) — never an unqualified `git push`. New `local-ahead` skip.
- Safe failure: unstage the team path when the commit fails (hook/GPG/identity)
  so a user's next manual `git commit` cannot sweep team files in.
- Non-interactive git: force `GIT_TERMINAL_PROMPT=0` + batch-mode SSH, SIGKILL on
  timeout, and a shorter timeout so sync cannot block session start on a prompt.

config refresh: rebuild the team index BEFORE syncing (so a fresh MEMORY.md is
committed/pushed, with a re-build after a pull that brought new files) and key
the shareability latch by projectRoot so a newly entered repo is re-checked.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): harden team-memory index/scan/path security

Address the security/correctness criticals from PR #5886 review. Team
memory is committed to the repo and loaded into every collaborator's
system prompt, so its write/scan/classification paths are trust
boundaries.

- indexer: pass noFollow on the team MEMORY.md write and reject a
  symlinked team root, so a committed `.qwen/team-memory` (or index)
  symlink can't redirect the generated index outside the repo.
- indexer: sanitize attacker-controlled frontmatter title/description
  (strip control/zero-width/bidi chars, collapse newlines, defang
  code/link markdown, cap length) before embedding into MEMORY.md, to
  prevent prompt injection into the shared context.
- indexer: skip the rewrite when the generated index is byte-identical,
  avoiding mtime churn and no-op commit cycles.
- scan: normalize CRLF before parsing so a Windows checkout's team files
  don't silently drop out of the shared index.
- paths: resolve a dangling final-component symlink via lstat/readlink in
  realpathNearestExisting, so a `decoy -> team-memory/leak.md` (missing
  target) is classified as team and the secret scanner can't be bypassed.
- secret-scanner: bound both quantifiers in the openai-api-key T3BlbkFJ
  rule ({20,512}) to remove O(n^2) ReDoS backtracking; real keys still
  match.
- team-memory-git-status: also probe a representative topic path so a
  repo that re-includes the index but ignores topic files is flagged as
  non-shareable.
- notebook-edit: run the team-memory secret scan in validateToolParamValues
  too, for parity with edit/write-file.

Adds load-bearing tests for each fix.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): sanitize team index paths and verify whole-path containment

Refines the earlier team-memory security hardening with two fixes the
first pass left open:

- sanitizeIndexPath now defangs the attacker-controlled relativePath
  rendered into the committed MEMORY.md — at BOTH the `](path)` link
  target and the "(also: ...)" dedup suffix. A git filename may legally
  contain newlines and markdown delimiters, so a raw path could inject a
  second physical line (e.g. "- SYSTEM:") and reopen the prompt-injection
  structure the patch was closing for title/description.
- rebuildTeamAutoMemoryIndex now realpath-resolves the whole team root
  and requires it to equal repoRoot/.qwen/team-memory. The prior lstat
  only inspected the leaf, so a symlinked PARENT component (e.g.
  `.qwen -> /tmp/out`) let scan/write escape the repo undetected while
  the guard believed it had rejected symlinks.

Also adds a regression test pinning the security-load-bearing invariant
that isAnyAutoMemPath excludes team paths (so team writes stay ask-gated).

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): keep team index link targets addressable, not just safe

The earlier injection fix rewrote legal filename chars (`(` `)` `[` `]` and
backticks) to `_` and used that destroyed value as the ACTUAL MEMORY.md link
target. A real file `feedback/a(b).md` was emitted as `](feedback/a_b_.md)`,
which does not exist (or, worse, collides with a different real file) — the
link was injection-safe but no longer addressable.

Replace `sanitizeIndexPath` with `encodeIndexPathTarget`, a reversible
percent-encoder: every char outside an addressable allowlist (alphanumerics
and `/ . - _ ~`) is percent-encoded, so breakout chars become inert ASCII
(newline->%0A, `(`->%28, `)`->%29, space->%20, backtick->%60, ...). The target
is a single line with no `](`/`)` breakout, yet `decodeURIComponent` recovers
the exact original path so the link resolves to the real file. `/` is kept
literal so the path still works as a relative reference. Applied at both index
sites (the main `](path)` link and the `(also: ...)` suffix). The display label
(title) stays non-reversibly sanitized as before — it is not addressable.

Update the path tests to assert the emitted target percent-decodes back to the
real relative path, and add a `feedback/a(b).md` case. The newline+`]`/`)`
injection assertions remain (mutation-verified: they fail if the encoding is
dropped).

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): gate team-memory sync on the index safety check

rebuildTeamAutoMemoryIndex throws when the team root is a symlink that
could redirect the committed index outside the repo, but the surrounding
.catch swallowed the error and syncTeamMemory ran independently — so a
refused symlink/escape still got git add/commit/push'd on the out-of-repo
dir, defeating the indexer's refusal. Capture whether the rebuild
succeeded and only sync when it did, so a failed safety check skips sync
entirely.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): harden team-memory git sync and cover execute-time secret scan

Several robustness fixes to the team-memory git sync, plus a missing test:

- Respect a user's GIT_SSH_COMMAND: append our non-interactive batch guards
  (-oBatchMode=yes -oConnectTimeout=5) onto it instead of clobbering it, so a
  custom identity (-i), proxy jump (-J), or non-standard port is preserved.
- Skip cleanly on a detached HEAD (new `detached-head` skippedReason) instead
  of creating an orphaned commit that can never be pushed. A branch with no
  upstream still commits locally as before.
- Use SIGTERM (not SIGKILL) as the git kill signal so a timeout mid-commit/-pull
  lets git release index.lock and clean up; the non-interactive ssh guards
  already bound the network-hang case.
- docs(memory): correct the sync description to match the implementation
  (rebuild index, ff-pull BEFORE commit, push only the scoped sync commit,
  skip on divergence/no-upstream).
- test(notebook-edit): cover the execute-time team-memory secret scan (the
  full-notebook backstop), distinct from the existing validate-time test.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): split security vs operational team-sync failures; per-op git kill signal

Refine the recent team-memory sync changes per review:

- indexer: throw a typed TeamMemoryRootSecurityError for the symlink/escape
  rejection so the sync gate can tell a SECURITY refusal apart from an
  OPERATIONAL IO failure. config now blocks sync ONLY on the security class;
  an operational rebuild failure (EACCES/ENOSPC/EPERM) is logged but
  self-corrects on the next rebuild and no longer permanently gates legitimate
  team sync. The security invariant (never add/commit/push an escaping root)
  is unchanged.
- team-memory-sync: pass a per-op kill signal to tryGit — SIGKILL for the
  hang-prone network/ref ops (pull/push/rev-parse/symbolic-ref; no index.lock
  to corrupt), SIGTERM for mutating ops (add/commit) so git can release locks
  and clean up. Node's execFile timeout does not escalate to SIGKILL on its
  own, so a child trapping SIGTERM could hang past the timeout.
- team-memory-sync: thread the resolved branch into resolvePushTarget so it no
  longer re-spawns `git symbolic-ref` after the detached-HEAD check.
- docs: fix a broken sentence in the team-memory git-sync section.
- tests: cover both rebuild failure classes (security -> sync skipped;
  operational -> sync still runs) and a positive gate (rebuild ok + sync
  enabled -> sync runs); assert the typed error on the indexer symlink tests.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <noreply@qwen.ai>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-27 12:29:48 +00:00
VectorPeak
a12ca8dca8
fix(desktop): harden remaining source path validation (#5914) 2026-06-27 06:30:55 +00:00
VectorPeak
51ec7c36f7
fix(desktop): reject unsafe source slugs before deletion (#5829)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* fix(desktop): reject unsafe source slugs before deletion

* fix(desktop): centralize source slug validation

* test(desktop): cover source slug deletion cases

* fix(desktop): preserve legacy source directory handling

* fix(desktop): preserve invalid source slug fallbacks

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-27 00:14:28 +00:00
顾盼
44b80da0db
feat(memory): confirm auto-generated skills before persisting (#5616)
* feat(memory): add memory.autoSkillConfirm setting schema

* feat(memory): add Config.getAutoSkillConfirmEnabled()

* feat(memory): wire memory.autoSkillConfirm through cli/acp/desktop settings

* feat(memory): add pending-skills staging helpers

* feat(memory): stage auto-skills for confirmation in runSkillReview

* feat(memory): pass autoSkillConfirm flag from client to skill review

* feat(memory): skill-review subscriptions + accept/reject pending APIs

* feat(cli): add skill-review dialog state to UI context

* feat(cli): add SkillReviewDialog component

* feat(cli): render SkillReviewDialog from DialogManager

* feat(cli): wire skill-review subscription and idle dialog routing

* feat(cli): show pending auto-skill review hint in footer

* feat(cli): add autoSkillConfirm toggle to /memory dialog

* docs(memory): document memory.autoSkillConfirm setting

* fix(cli): focus and Ctrl+C-close the skill-review dialog

* fix(memory): address review on auto-skill confirmation

- stage only newly-created skills, never agent-edited pre-existing ones, so
  Discard can't delete a skill the user already confirmed
- re-read pendingSkills after the await in resolvePendingSkill so concurrent
  Keep-all/Discard-all removes every entry, not just the last
- surface accept/reject fs failures (try/catch + log + .catch) instead of
  silently swallowing them
- remount SkillReviewDialog per task via key so its snapshot never goes stale
  across consecutive skill-review batches
- skip redundant skillReviewPending updates with a signature compare
- remove the unreachable openSkillReviewDialog action
- add debug logging to the pending-skills module
- ignore .qwen/pending-skills/ explicitly in .gitignore

* fix(memory): address round 2 review on auto-skill confirmation

- acceptPendingSkill: when the staged dir is gone, no-op only if the skill is
  already in the skills root; otherwise throw so resolvePendingSkill keeps it
  pending and logs, preventing silent data loss
- fall back to the agent's systemMessage for progress text when staging yields
  zero pending (a pre-existing-skill edit is still a durable change)
- log the no-task / no-target early returns in resolvePendingSkill
- replace internal tracker references in an AppContainer comment

* fix(memory): harden auto-skill confirmation for multi-batch and edge cases

- parseDescription: keep an empty description empty instead of spilling onto
  the next YAML line
- namespace staged dirs under the task id so a later same-named batch can't
  clobber a still-deferred earlier one
- track Esc-dismissed batches in a Set (not a single value) and only mark a
  batch dismissed on Esc, so a partially-failed Keep-all can reopen for the
  unresolved skills
- document the in-place updateRecord invariant the accept/reject race fix
  relies on
- add the missing license header to pending-skills.test.ts

* fix(memory): strip quoted descriptions; Ctrl+C defers skill-review dialog

- parseDescription: strip a matching pair of surrounding quotes so a
  `description: "..."` frontmatter value isn't rendered with literal quotes
- useDialogClose: Ctrl+C on the skill-review dialog now calls
  dismissSkillReviewDialog (records the batch as dismissed) instead of plain
  close, matching Esc — otherwise the idle effect immediately reopened it

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-25 00:47:16 +00:00
Dragon
f4b591b71c
feat(desktop): show file preview in a resizable side panel instead of fullscreen (#5730)
* feat(desktop): show file preview in a resizable side panel instead of fullscreen

Clicking a previewable file now opens it in a docked, resizable panel on the
right while the conversation and file tree stay visible and interactive,
instead of a fullscreen overlay that took over the whole window.

- Add embedded/docked render mode to the overlay base components (FullscreenOverlayBase
  and the Image/PDF/Markdown overlays) so previews can fill a panel without a modal portal.
- Add FilePreviewPanel: a left-edge drag-to-resize docked panel with width persisted to localStorage.
- Mount the preview beside AppShell as a split layout instead of a fullscreen sibling.

Closes #4885

* fix(desktop): route file paths to in-app preview instead of the browser

Two markdown link-detection issues made file paths fail to open in the preview:

- Bare filenames whose extension doubles as a TLD (readme.md → .md Moldova,
  main.py → .py Paraguay, config.sh → .sh St. Helena) were auto-linked as web
  URLs and opened a parked/ad domain in the browser. Scheme-less linkify matches
  that are actually local file paths are now treated as files.
- Paths with non-ASCII (e.g. CJK) segments like /Users/me/项目/笔记.md were not
  detected at all because the path regex used ASCII-only \w classes, so they
  never became clickable. The path regexes are now Unicode-aware (\p{L}\p{N} + u flag).

Adds regression tests for both cases.
2026-06-23 02:38:48 +00:00
tt-a1i
0880e34461
fix(desktop): reject fractional transfer sizes (#5527) 2026-06-21 13:05:52 +08:00
tt-a1i
71fd0294e9
fix(desktop): consolidate path boundary checks (#5545) 2026-06-21 13:05:36 +08:00
tt-a1i
948f78279a
fix(desktop): handle Windows file mentions (#5523) 2026-06-21 12:49:43 +08:00
tt-a1i
0eb4395c53
fix(desktop): separate transform data output lines (#5525) 2026-06-21 12:42:57 +08:00
tt-a1i
19573f78ca
test(desktop): enable feedback flag in permission tests (#5533) 2026-06-21 12:21:30 +08:00
tt-a1i
aeb0e5810a
fix(desktop): keep sibling paths absolute (#5517) 2026-06-21 12:21:07 +08:00
tt-a1i
b117c56b46
test(desktop): align interceptor packaging contract (#5531) 2026-06-21 12:16:15 +08:00
tt-a1i
ca7638e85c
fix(desktop): allow double dots in bundle filenames (#5515) 2026-06-21 06:16:50 +08:00
tt-a1i
83f51469cf
fix(desktop): validate generic oauth token responses (#5511) 2026-06-21 06:02:20 +08:00
tt-a1i
2a7753658c
fix(desktop): parse server ports strictly (#5509) 2026-06-21 06:02:00 +08:00
tt-a1i
e4fdd24217
fix(desktop): restore locale parity (#5537) 2026-06-21 06:00:52 +08:00
tt-a1i
0c65dbcaa1
test(desktop): update blocked scheme open-url assertion (#5529) 2026-06-21 05:59:43 +08:00
tt-a1i
8368e7fb06
fix(desktop): parse NO_PROXY ports strictly (#5498) 2026-06-21 02:16:59 +08:00
tt-a1i
172288bc03
fix(desktop): preserve uppercase favicon URLs (#5463) 2026-06-21 02:14:21 +08:00
tt-a1i
26c13091d4
fix: accept uppercase endpoint URL schemes (#5443)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
2026-06-21 02:09:29 +08:00
tt-a1i
cf3e57fa50
fix(desktop): accept uppercase icon URL schemes (#5470) 2026-06-21 01:52:01 +08:00
Yufeng He
5479b44749
fix(desktop): detect WebP and AVI in RIFF magic-byte sniffing (#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.
2026-06-19 07:59:30 +08:00
Matt Van Horn
0d15e89a1a
fix: Expand Windows ~\\ home paths and hide phantom (session) entries in the desktop session list (#5253) 2026-06-19 07:11:29 +08:00
pomelo
f91054a32f
feat(desktop): compile macOS 26+ Liquid Glass Assets.car in brand-create (#5284)
The brand-create script previously only produced icon.icns for macOS.
On macOS 26+, apps with only icon.icns render the legacy icon and miss
the new Liquid Glass icon treatment. The afterPack hook already copies
a pre-compiled Assets.car from resources/brands/<brand>/ when present,
but brand-create never generated one — every brand emitted the warning
"Pre-compiled Assets.car not found".

Teach brand-create to build Assets.car automatically on macOS hosts:

- Emit an AppIcon.appiconset inside a temporary Assets.xcassets using
  the same icon sizes (16..1024) already produced for icon.iconset.
- Compile with xcrun actool --app-icon AppIcon --platform macosx and
  copy the resulting Assets.car into the brand resources dir.
- Clean up the xcassets scratch directory and the tmp compile output
  on both success and failure paths.
- Gracefully fall back to icon.icns (returning hasAssetsCar: false)
  when actool is missing or returns a non-zero exit, so non-macOS
  hosts and environments without Xcode still produce a valid brand.

The returned BrandAssetsResult now carries hasAssetsCar through to
brandBlock(), which emits the optional liquidGlassAssetsCar field in
branding.ts only when the compile succeeded — matching the existing
convention used by the qwen-code and openwork brands.
2026-06-19 03:57:02 +08:00
callmeYe
b524423dfd
feat(extensions): add i18n support for extension displayName and description (#5289)
* feat(extensions): add i18n support for qwen-extension.json displayName and description

Allow extension authors to provide locale maps for `displayName` and
`description` fields in qwen-extension.json. The CLI resolves to the
correct language at load time, and re-resolves dynamically at display
time when the user switches language mid-session via `/language ui`.

Fallback chain: exact locale → base language → en → first available value.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(extensions): show description in extension detail view

Display the localized description field in the interactive
/extensions manage detail panel, between name and version.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(extensions): replace version with description in extension list

Show localized description (max 2 lines, truncated with ellipsis)
instead of version number in the interactive /extensions manage list.
Version is still visible in the detail view.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(extensions): show description instead of version in /extensions list

Replace version number with localized description in the interactive
/extensions list output. Long descriptions are truncated at 80 chars
with ellipsis.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-18 09:31:50 +00:00
tt-a1i
d01197ca5f
fix(desktop): address git branch badge review (#5247) 2026-06-18 17:11:02 +08:00
jinye
1e6b9d6fb3
fix(cli): Preserve mid-turn image messages (#5183)
* fix(cli): Preserve mid-turn image messages

Preserve image attachments and structured content when user messages are drained during tool execution. Keep text-only behavior compatible while preventing attachment-only or missing-base64 image messages from being acknowledged before they can be replayed safely.

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

* codex: address PR review feedback (#5183)

Fix the lint blockers, preserve legacy mid-turn messages when structured items are empty, share the mid-turn prefix helper, and record mid-turn @ command reads without adding UI tool groups.

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

* fix(cli): Harden mid-turn message drain

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

* fix(cli): Enforce mid-turn resolve timeout

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

* fix(cli): Address mid-turn review suggestions

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

* codex: address PR review feedback (#5183)

Bound mid-turn @ resolution with a per-message timeout and surfaced attachment failures to the user before falling back to raw text.

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

* codex: address PR review feedback (#5183)

Surface mid-turn at-command tool displays, validate ACP inline media MIME prefixes, and add focused coverage for mid-turn message prefixing.

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

* codex: address PR review feedback (#5183)

Stop processing drained mid-turn messages once cancellation aborts at-command resolution, avoiding stale fallback notifications and recordings.

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

* fix(cli): Address mid-turn review feedback

Skip mid-turn @ injections when attachment resolution fails, acknowledge image-only desktop queue entries by optimistic id, and make structured ACP mid-turn failures visible through a fallback notice and diagnostics.

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

* fix(cli): Harden mid-turn drain edge cases

Handle additional mid-turn drain review feedback by logging unknown drain schemas, preserving accumulated parts during abort, acknowledging drained image-only messages more robustly, and avoiding empty text blocks for attachment-only prompts.

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

* test(cli): Cover mid-turn drain review cases

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

* fix(desktop): Remove unused mid-turn attachment argument

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

* fix(desktop): Harden mid-turn drain acknowledgements

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

* fix(cli): Skip failed mid-turn at-command injection

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

* fix(desktop): Simplify mid-turn drain acknowledgements

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

* fix(desktop): Use stable mid-turn drain ack keys

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

* fix(cli): Handle mid-turn image drain failures

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

* fix(cli): Bound mid-turn drain fallbacks

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

* fix: Bound mid-turn attachment handling

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

* fix: Harden mid-turn attachment edge cases

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: 易良 <1204183885@qq.com>
2026-06-18 09:24:55 +08:00
qwen-code-ci-bot
ee8fb80010
chore(release): desktop desktop-v0.0.4 [skip ci]
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-15 21:43:07 +08:00
tt-a1i
a629dd0ab6
feat(desktop): show git branch in working directory badge (#5082) 2026-06-15 16:58:45 +08:00
Dragon
a969c84620
fix(desktop): isolate update feed from CLI releases (#5139) 2026-06-15 15:32:39 +08:00
Dragon
04412163ab
fix(desktop): allow unsigned Windows auto-updates (#5028) 2026-06-12 16:53:08 +08:00
Dragon
5121c6563e
perf(desktop): add --cli-only flag to skip non-CLI packages during vendor build (#5025)
The desktop vendor step only needs the CLI bundle, but was building all
14 workspaces including webui, sdk, web-shell, and vscode-ide-companion.
This wasted ~30-40% of build time and triggered TS type errors in
vscode-ide-companion on newer Node.js versions.

Add a --cli-only flag to scripts/build.js that truncates the build
order after the CLI package. vendor-qwen-code.ts now passes this flag
when building from a local source checkout, so both local brand builds
and CI desktop-release (source_branch mode) benefit automatically.
2026-06-12 16:52:37 +08:00
Dragon
3c55295f63
docs(desktop): use main for brand builder skill (#5021) 2026-06-12 16:52:21 +08:00
顾盼
963fc543d1
ci(desktop): mac code-signing + App Store Connect API-key notarization (#5013)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* chore(desktop): drop dead NOTARIZE env flag from mac signing paths

electron-builder (>=24) auto-notarizes via notarytool whenever APPLE_ID,
APPLE_APP_SPECIFIC_PASSWORD, and APPLE_TEAM_ID are present in the env. The
NOTARIZE=true flag set in the release workflow, build-dmg.sh, and
scripts/build/darwin.ts was never read by electron-builder, and the
build-dmg.sh comment claiming it enabled notarization was misleading.
Remove the no-op and document the actual auto-detection behavior.

* ci(desktop): notarize via App Store Connect API key instead of Apple ID

Switch the macOS desktop release notarization path from the Apple ID +
app-specific password method to the App Store Connect API key method,
which is more robust (no 2FA, no password expiry) and reuses the notary
key already provisioned for the org.

The signing step now reads APPLE_NOTARY_API_KEY_P8_BASE64,
APPLE_NOTARY_KEY_ID, and APPLE_NOTARY_ISSUER_ID, decodes the .p8 to a
temp file, and exports APPLE_API_KEY/APPLE_API_KEY_ID/APPLE_API_ISSUER,
which electron-builder (>=24) consumes to notarize via notarytool.
Published mac releases now require those notary secrets plus
APPLE_TEAM_ID.
2026-06-12 13:14:43 +08:00
Dragon
62e45c567f
feat(desktop): Add desktop app package with Qwen ACP SDK integration (#3778) 2026-06-11 21:57:20 +08:00