Commit graph

5766 commits

Author SHA1 Message Date
jinye
637d00cebc
feat(core): render PDF pages to images when text extraction overflows or fails (#6585)
* feat(core): render PDF pages to images when text overflows or fails

Replace the PDF text-only 12000-token dead-end with a bounded page->image
fallback for vision-capable models, mirroring claude-code's approach.

- pdf.ts: add renderPDFPagesToImages / isPdftoppmAvailable (pdftoppm -jpeg
  -scale-to), with timeout, error mapping, and a total-bytes cap.
- fileUtils.ts: text-first, then four ordered fallbacks on overflow/failure:
  (1) render to the vision main model (explicit read, <=20 pages),
  (2) render <=4 pages to the vision bridge (text-only model, scanned @-PDF),
  (3) reference (no-pages @-attach), (4) text too-large guidance / error.
  Widen ProcessedFileReadResult.llmContent to PartListUnion.
- read-file.ts / readManyFiles.ts / pathReader.ts: handle the Part[] payload;
  pathReader now references large @-PDFs, aligning the two @ paths.

WIP: tests not yet migrated.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* test(core): cover PDF page rendering and vision-bridge fallbacks

- pdf.test.ts: renderPDFPagesToImages (success/sort, page range, Infinity
  last page, unavailable, password, corrupt, empty output, byte-cap).
- fileUtils.test.ts: mock renderPDFPagesToImages and assert vision-model
  page/whole-doc/scanned rendering, >20-page guidance, truncation notes,
  renderer-unavailable fallback, and text-only bridge rendering (scanned @
  PDF -> <=4 pages, page-count note, text-first reference, no-flag error).

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(core): never drop PDF pages silently when page count is unknown

Audit follow-up. Both image-render fallbacks could omit pages without a
note when pdfinfo is unavailable (page count null):

- vision no-pages render: when the size heuristic underestimates and the
  render fills the per-read page ceiling, add a note (previously only the
  byte-cap path was flagged).
- vision-bridge render: when the page count is unknown and the render hits
  the image cap, flag it (previously required a known count). Soften the
  note wording to not over-promise a later-range read on the @ path.

Adds regression tests for both unknown-page-count paths.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
2026-07-09 10:09:44 +00:00
ytahdn
e64010c116
Fix workspace skills for disabled extensions and ACP preheat (#6534)
* fix(cli): keep workspace skills in sync with extensions

* fix(cli): address workspace skills review feedback

* test(cli): cover synthesized inactive extension skills

* fix(cli): address workspace skills review issues

* fix(cli): address workspace skills review followups

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-07-09 09:11:17 +00:00
ermin.zem
5c82857fea
Add harness infrastructure for web-shell package (#6517)
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
E2E Tests / web-shell Browser Regression (push) Waiting to run
* test(web-shell): add browser and lint harness

* test(web-shell): harden browser smoke harness

* fix(web-shell): guard mock daemon model state

* test(web-shell): remove unused scenario harness

* fix(web-shell): remove stale lint disables

* test(web-shell): make matchMedia stub writable

* fix(web-shell): exclude tests from package typecheck

* test(web-shell): tighten mock daemon route contract

* Update packages/web-shell/client/e2e/utils/mockDaemon.ts

Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>

* test(web-shell): clear stale SSE connections

* ci(web-shell): gate smoke on full CI profile

---------

Co-authored-by: ermin.zem <ermin.zem@alibaba-inc.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: 易良 <1204183885@qq.com>
2026-07-09 08:11:58 +00:00
jinye
c9a80996d4
feat(cli): List persisted sessions for trusted workspaces (#6558)
* feat(cli): List persisted sessions for trusted workspaces

Add trusted non-primary active persisted session discovery for plural workspace session list routes. Preserve live-only fallback behavior when no active persisted sessions exist, and keep archived or organized non-primary list options gated.

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

* codex: address PR review feedback (#6558)

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

* codex: stabilize workspace session cursors (#6558)

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-07-09 06:20:45 +00:00
ytahdn
48e5d5d0d7
feat(web-shell): polish stats table layout and todo panel UI (#6559)
* feat(web-shell): polish stats table layout and todo panel UI

- Use CSS grid for model usage table with fixed column widths
- Add loading spinner for in_progress todo items
- Add strikethrough for completed todo items
- Introduce nested variant for PivotRow to show thoughts as output sub-item
- Clarify i18n labels: stats.prompt -> Input Tokens, stats.output -> Output Tokens

* fix(web-shell): address review suggestions for stats table and todo panel

- Add prefers-reduced-motion media query for todo spinner (accessibility)
- Right-align numeric columns in model usage table for magnitude comparison
- Consolidate duplicate i18n keys (stats.prompt/output → stats.inputTokens/outputTokens)

* fix(web-shell): address stats review feedback

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
Co-authored-by: 易良 <1204183885@qq.com>
2026-07-09 06:06:51 +00:00
Changxiao Ruan
8c896f6b09
fix(web-shell): make dialog backdrop z-index configurable (#6572) 2026-07-09 06:05:30 +00:00
顾盼
befa937375
fix(mobile-mcp): strip bounds attributes from UI hierarchy dump (#6568)
Remove bounds coordinates from mobile_ui_dump XML output to reduce
token consumption when LLMs process the hierarchy tree.
2026-07-09 03:59:00 +00:00
chinesepowered
d8084c63bc
fix(serve): stop cdp-mcp-command reading process.env directly (#6562)
cdp-mcp-command.ts read process.env directly — via a `= process.env` default on resolveCdpMcpCommand and a direct read in isBrowserAutomationMcpAvailable — which trips the serve process.env guard test (process-env-guard.test.ts) and fails CI on main.

Thread env through both helpers instead: they now take env explicitly, supplied by the already-allowlisted boundary callers (acp-http, run-qwen-serve, serve-features). Behavior is unchanged.
2026-07-09 03:34:45 +00:00
callmeYe
6e48077532
fix(daemon): surface workspace memory task error details (#6431)
* fix(daemon): surface workspace memory task error details

* fix(daemon): harden workspace memory error details

* fix(daemon): cover workspace memory detail edge cases

* fix(daemon): harden workspace memory detail extraction

* fix(daemon): harden workspace memory failure diagnostics

* fix(daemon): sanitize workspace memory diagnostics

* fix(daemon): sanitize workspace memory debug logs

* fix(daemon): preserve sanitized memory task stack logs

* fix(daemon): harden memory diagnostics redaction

* fix(daemon): refine memory task diagnostics

* fix(daemon): preserve workspace memory stack diagnostics

* fix(daemon): harden workspace memory diagnostics

* fix(daemon): guard workspace memory error code extraction

* fix(daemon): share workspace memory extraction logging

* fix(daemon): suppress workspace memory unavailable details

* fix(daemon): clarify workspace memory unavailable timeout logs

* fix(daemon): preserve memory diagnostic separators

* fix(daemon): harden workspace memory failure handling

* fix(daemon): redact split platform tokens

* fix(daemon): redact split memory error credentials

* fix(daemon): harden workspace memory error diagnostics
2026-07-09 01:44:48 +00:00
callmeYe
25423b1526
fix(cli): align memory dialog with managed memory (#6434)
* fix(cli): align memory dialog with managed memory

* test(cli): stabilize memory dialog path rendering

* fix(cli): make memory target switch exhaustive

* fix(cli): tighten memory dialog target handling

* fix(cli): handle headless managed memory dialog

* test(cli): cover desktop managed memory dialog branches

* fix(cli): open memory folders asynchronously

* test(cli): assert managed memory folder setup

* fix(cli): simplify memory folder opener

* fix(cli): clarify memory folder opener behavior
2026-07-09 01:04:56 +00:00
Aleks-0
4d5015389d
chore(core): remove stale refreshStartupContextReminder mocks from tool-search tests (#6423)
Two inline mock clients still stubbed refreshStartupContextReminder after it was removed from tool-search.ts. makeConfigWithRegistry() was cleaned up in the original PR but these two were missed.

Co-authored-by: Aleks-0 <aleks-0@users.noreply.github.com>
2026-07-09 01:04:47 +00:00
C0d3N1nja97342
3b20a12436
fix(extension): clean tempDir before fallback git clone on Windows (#6545)
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
downloadFromGitHubRelease can write a partial archive (or extracted files)
into tempDir before failing - e.g. a repo whose latest GitHub release is a
source tarball that isn't a valid extension archive. The catch block then
reused that dirty tempDir for cloneFromGit, where `git clone <url> ./` fails
with "destination path '.' already exists and is not an empty directory".

On Windows this surfaces as a hard install failure for any extension repo
that has a GitHub release but isn't structured as a release-asset extension
(reproduced with https://github.com/Imbad0202/academic-research-skills).

Recreate a clean tempDir (rm + mkdir) before the fallback clone so the
git clone always runs on an empty directory.

Fixes #6334
2026-07-09 00:39:15 +00:00
Dex
e3a247f99e
perf(core): add pure-ASCII fast path to text token estimation (#6551)
estimateTextTokens scanned every string char-by-char via charCodeAt to
classify ASCII vs non-ASCII code units. For pure-ASCII text (code,
English prose - the common case) a single regex scan using V8's
optimized string search replaces the JS loop, and the mixed-text path
now counts only non-ASCII units, deriving the ASCII count from the
length. The token formula is unchanged, so results are byte-identical
for every input; verified exhaustively over all 65536 single code units
plus 20k randomized mixed strings against the previous implementation.

Median of 6 solo benchmark runs over a deterministic mixed fixture set:
51.9ms -> 32.2ms (-38%, 1.61x).
2026-07-08 23:39:02 +00:00
易良
fbdaa52c52
Gate browser automation MCP on external adapter (#6472)
* feat(cli): gate browser automation adapter

* fix(cli): close browser automation review gaps

* test(cli): cover browser automation gates

* fix(cli): close browser automation review gaps

* fix(cli): close browser automation review gaps
2026-07-08 23:26:44 +00:00
易良
10fa9effbb
fix(shell): avoid self-kill from pgrep selectors (#6544)
* fix(shell): avoid self-kill from pgrep selectors

Fixes #6246

* fix(shell): handle pgrep review cases
2026-07-08 23:25:52 +00:00
Nothing Chan
0a54652e07
fix(core): configurable vision bridge timeout + retry with fresh budget (#6541)
* fix(core): configurable vision bridge timeout + retry with fresh budget

The vision bridge capped image transcription at a hardcoded 30s. On a
slow or proxied vision endpoint one latency spike permanently lost the
image: the retry inside the side query shared the same abort signal, so
a second attempt inherited whatever seconds were left of the first
attempt's budget.

Add a visionBridgeTimeoutMs setting (per attempt; unset keeps 30s,
non-positive values are ignored) and retry a timed-out attempt once at
the bridge level with a freshly created timeout signal. Non-timeout
failures still fail immediately, and user cancellation is still
reported as skipped.

Fixes #6524

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core): harden visionBridgeTimeoutMs against invalid timer values

Maintainer E2E review found that fractional or out-of-range values such as 30000.5 and 4294967296 could pass the old number-typed config path and Config's Number.isFinite && > 0 guard. Node rejects fractional AbortSignal.timeout values with RangeError and can degrade oversized timer values to a 1ms timeout, which made image turns fail before any model request.

Tighten the Config guard to positive integers within the supported 32-bit timer ceiling, make visionBridgeTimeoutMs a bounded integer setting so /config and the generated JSON schema reject bad values up front, and move AbortSignal.timeout/any creation inside the bridge try block so any future bad value becomes a safe failure result instead of an escaped rejection. Also mark the setting requiresRestart because it is read once in the Config constructor.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 23:24:39 +00:00
qqqys
b8b3287308
fix(channels): add chat payload diagnostics (#6539)
* fix(channels): improve wecom mention diagnostics

* fix(channels): harden diagnostic logging

* fix(channels): redact platform sender identities

* test(channels): assert debug silence before restoring spies

* fix(channels): log pairing-required preflight drops

* fix(channels): compact debug payload logs

* test(channels): expect compact debug payload logs
2026-07-08 23:23:47 +00:00
Dragon
6dafb330f2
docs: fix model-provider config shape and refresh feature/setting drift (#6552)
Audit findings against the current codebase:

- model-providers.md, auth.md: the documented modelProviders shape used
  the reverted `{ protocol, models }` wrapper. The canonical shape is a
  bare `ModelConfig[]` array per provider id (a wrapped entry in a
  migrated settings file is silently skipped). Update all examples and
  prose, document the separate top-level `providerProtocol` map for
  custom provider ids, and correct the unknown-key behavior.
- settings.md: correct the default for
  `model.chatCompression.screenshotTriggerThreshold` (20, not 50).
- commands.md: add the missing `/reload-plugins` command and note that
  `/dream` and `/forget` are registered only when managed auto-memory
  is available.
- Add a Computer Use feature page (on-by-default desktop automation via
  the cua-driver native driver) and wire it into the features nav and
  the qc-helper doc index.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-08 23:04:47 +00:00
jinye
393943daaf
feat(cli): Add session owner index for workspace runtimes (#6540)
* feat(cli): Add session owner index for workspace runtimes

Route live session ownership through a registry-backed owner index so multi-workspace sessions can resolve active sessions without scanning every bridge first. Expand trusted workspace load/resume and live read routing while keeping non-session surfaces primary-only.

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

* fix(cli): avoid partial session owner index updates

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

* test(cli): relax bridge wiring test timeout

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

* fix(cli): tighten workspace session owner routing

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

* fix(cli): normalize restore workspace mismatch handling

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

* fix(cli): record telemetry for workspace sessions alias

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

* fix(cli): preserve workspace selector error contract

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-08 22:35:53 +00:00
Nothing Chan
87cad6f1ae
feat(memory): make background memory agent timeouts configurable (#6459)
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
* feat(memory): make background memory agent timeouts configurable

Adds a memory.agentTimeoutMinutes setting that overrides the hardcoded
max runtime of the four background memory agents (extraction, dream,
remember, skill review). Unset keeps each agent's built-in default
(2-5 minutes); 0 disables the time limit entirely.

Local LLM setups load large extraction prompts far slower than hosted
models, so the fixed 2-minute extractor budget times out before the
context even finishes loading — and each retry carries a longer
conversation, making the next timeout more likely.

Fixes #6308

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(memory): address review — wire agentTimeoutMinutes to skill review, clamp negatives, add tests

The auto-skill scheduling path always passed an explicit timeoutMs, so
the new setting never reached the skill review agent; drop the redundant
pass-through so the planner's config fallback applies. Clamp negative
settings values at the Config constructor (schema validation only runs
on interactive edit paths). Add positive override tests for the dream,
remember, and skill review planners, and reduce the settings.md diff to
the single new table row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(memory): cover negative-clamp and remember default-timeout paths

Review follow-up: assert the Config constructor treats a negative
memory.agentTimeoutMinutes as unset, and that the remember planner keeps
its built-in 5-minute default when nothing is configured.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-08 16:47:07 +00:00
AlexHuang
74ebb10e8b
fix(cli): prefer command name match over alias match regardless of recentScore (#6504)
In compareRankedCommandMatches, recentScore was evaluated before nameVsAlias in the sort chain, causing recently-used alias matches to shadow name matches at the same matchStrength level.

The fix swaps the order so nameVsAlias is checked before recentScore, ensuring that a command matched by its primary name always ranks above an alias match, with recency acting as a tie-breaker only within the same match-type bucket.

Adds a regression test that gives an alias match a recentScore and verifies the name match still ranks first.

Signed-off-by: Alex <alex.tech.lab@outlook.com>
2026-07-08 16:34:51 +00:00
jinye
65c0d36be3
fix(session): detect and mark broken history chains instead of silently truncating (#6502)
* fix(session): bridge broken parentUuid chains instead of truncating history

reconstructHistory walked parentUuid from the newest leaf and stopped at the first missing ancestor, silently dropping every earlier record. A session file with a broken chain (a partial write, or a lost middle segment) therefore lost all history before the break on resume — in both the terminal /resume and the web-shell/ACP replay, which both go through sessionService.loadSession.

Add a shared hardened chain walk (buildOrderedUuidChain) that, on a missing parent, bridges onto the newest still-present earlier connected component (union-find; position-based). It treats /rewind gap children as a barrier and matches the tail's sidechain-ness, so it never resurrects abandoned rewind branches or crosses the main/subagent boundary. sessionService and background-agent-resume now share it.

loadSession returns historyGaps metadata; the terminal /resume and ACP replay render a localized (i18n) visible divider so the recovered halves are not read as contiguous. The bridged child's parentUuid is rewritten (on the aggregated copy) to the bridged record so rebuildTurnBoundaries/rewind re-root correctly.

Read-side only; write-side durability is a separate follow-up.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* qwen: fix CI failure on PR #6502

* fix(session): use non-recovered copy for history gaps with no bridged island

When a missing-parent gap has no earlier island to bridge onto (bridgedToUuid is null), the divider is the first visible item — the previous copy still said "recovered earlier history is shown above" with nothing above it. Emit a distinct notice for the null-bridge case (both terminal /resume and ACP replay go through formatHistoryGapNotice).

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(i18n): add zh-TW translation for the non-recovered history-gap notice

zh-TW is a strict-parity locale, so the new null-bridge notice key must be translated there too (pre-empts a CI strict-parity failure).

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(session): address history-gap review nits (accuracy, lazy maps, docs, tests)

- conversation-chain: gap duration now uses the target island's last-occurrence timestamp; a uuid can span several streamed records, and the first occurrence overstated the gap.
- conversation-chain: build posByUuid/lastByUuid lazily on the first gap (healthy sessions skip them); early-return for a caller-supplied leafUuid not backed by any record.
- resumeHistoryUtils: reorder createHistoryGapItem so the convertToHistoryItems JSDoc documents its own function again.
- HistoryReplayer: add tests covering the gap-notice replay path (notice emitted before the gap child; none when there are no gaps).

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(acp): thread historyGaps through the qwen/session/loadUpdates replay path

collectHistoryReplayUpdates now accepts gaps from both callers; the loadUpdates ACP surface passed only records, so a bridged (recovered) history was rendered contiguous there with no gap divider. Adds a loadUpdates test asserting the gaps reach the replayer.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(session): detect-and-mark broken history chains instead of stitching

Read-side, a record whose parentUuid is physically missing is indistinguishable from a lost /rewind marker — where the "earlier" turns are ones the user deliberately discarded. Speculatively stitching the nearest earlier island back on (the previous approach) could therefore resurrect deleted content (wenshao's [Critical]). This mirrors claude-code, which never guesses: it only reconnects across a gap when durable metadata (snip removedUuids, compact re-root) proves it safe, and otherwise truncates.

Replace the connected-component bridging with detect-only: on a missing parent the walk stops (as it always did) and records a HistoryGap, so the terminal /resume and ACP replay surfaces show a visible "earlier history was lost and could not be recovered" marker instead of silently truncating. No earlier records are reconstructed. Renames the option bridgeGaps -> detectGaps, drops the bridgedToUuid/approxLostMs fields and the now-unused "recovered above" i18n copy, and adds a regression test where the rewind marker is missing and the discarded branch must not be restored.

True recovery (keeping the earlier island) requires durable write-side metadata and is left as a follow-up.

* refactor(session): correct stale gap comments and dedup gap indexing

The detect-only rewrite (13c613c9) left doc comments that still described
the removed stitching path — claiming the earlier history was "bridged" or
"stitched back on". Reword them to match the actual behavior: the break is
detected and marked, the lost segment is not recovered.

Also extract the duplicated gap-by-child map construction (identical in both
HistoryReplayer.replay and resumeHistoryUtils.convertToHistoryItems) into a
shared indexGapsByChild helper alongside formatHistoryGapNotice — the one
still-applicable item from the review suggestion summary.

Comments + one small refactor only; no behavior change.

* fix(session): reset pending @-command state at a history-gap divider

Belt-and-braces for the resume renderer: when convertToHistoryItems emits a
history-gap divider it already flushes the pending tool group; also clear
pendingAtCommands so an unconsumed pre-gap at_command can never be shift()-
paired with the post-gap user turn (which would attach @file reads to a turn
the user never wrote them on).

In the current detect-only design reconstructHistory truncates to the tail
island — the gap child is always the first replayed record, so the buffer is
already empty at the divider and this cannot trigger. The reset keeps the
invariant if that ever changes. Adds a regression test at the
convertToHistoryItems boundary.

* fix(session): don't detect history gaps on the background-agent resume path

Addresses wenshao's review: this non-interactive transcript recovery has no
surface to render a gap marker on (unlike interactive /resume via sessionService
and the ACP replay via HistoryReplayer), so passing detectGaps: true only to
emit a debugLogger.warn and then drop the gaps was an inconsistent half-measure
("half-detection is worse than no detection"). Turn detection off here — the
walk truncates at a broken parent link either way, matching this path's
historical behavior. Gap surfacing stays exactly on the two paths that have a
UI for it.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-08 16:02:58 +00:00
易良
016d624021
fix(core): detect subagent tool call loops (#6543) 2026-07-08 15:51:09 +00:00
qwen-code-ci-bot
b330ec884f
chore(release): v0.19.8 (#6549)
* chore(release): v0.19.8

* docs(changelog): sync for v0.19.8

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-08 15:51:03 +00:00
Shaojin Wen
b2bee7040e
fix(web-shell): stabilize slash command i18n in split-view panes (#6546)
Split-view panes showed English descriptions for slash commands while
the main view showed Chinese. Two root causes:

1. ChatPane never merged getLocalCommands(t) into the command list,
   so ~33 built-in commands (help, model, clear, etc.) lacked i18n
   descriptions.

2. localizeBuiltinDescriptions required source === 'builtin-command',
   but the daemon omits _meta.source in some SSE event paths
   (available_commands_update), causing built-in commands to skip
   translation unpredictably across sessions.

3. Skill localization depended on connection.skills, which can be
   empty when SSE events deliver commands without availableSkills.

Fix: make the entire localization pipeline name-based and
session-independent — merge local commands, relax the source guard
to also translate when source is missing, and use skillDescriptionKey
directly instead of connection.skills for skill tagging.

Also adds missing autofix skill translation (EN + ZH).
2026-07-08 14:57:12 +00:00
Nothing Chan
5f41b166e6
fix(cli): unblock /clear after task cancellation and surface the blocked reason (#5949) (#6499)
/new (alias of /clear) silently did nothing when typed right after
cancelling a request, for two stacked reasons:

- hasBlockingBackgroundWork() gated on the registry's
  hasUnfinalizedTasks(), which counts cancelled-but-not-yet-finalized
  entries. That clause exists for the headless holdback loop (every
  task_started must pair with a task_notification), but /clear and
  session resume abort-and-reset the registry right after the gate —
  suppressing that very notification — so blocking on it only made the
  switch fail in the window between cancel and finalizeCancelled().
  The gate now keys off a new BackgroundTaskRegistry.hasRunningTasks(),
  which counts only entries still actually executing; the headless
  holdback keeps using hasUnfinalizedTasks() unchanged.
- When genuinely blocked, interactive mode showed only a transient
  debug line while non-interactive returned a proper error. Interactive
  now returns the same visible error message, so a blocked /clear no
  longer looks like a no-op.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 14:52:18 +00:00
Nothing Chan
082b3bb3d9
fix(memory): give each linked git worktree its own auto-memory root (#6462)
getAutoMemoryRoot() resolved linked worktrees back to the canonical
repository root, so every worktree of a repo shared one project memory.
Focused worktree sessions polluted the shared MEMORY.md index with
unrelated entries, and chats/, workflows/, and team memory were already
per-worktree — project memory was the only shared exception.

Anchor project memory at the nearest git root (same resolution team
memory already uses) so each worktree gets its own memory directory.
The main checkout resolves to the same path as before, so existing
memory is unaffected.

Fixes #6449

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 14:34:03 +00:00
Nothing Chan
e935141f8e
fix(cli): fixed-width elapsed time below one minute to stop status-line jitter (#6533)
* fix(cli): fixed-width elapsed time below one minute to stop status-line jitter

The loading indicator ticks at 0.5s resolution, so the time string
alternated between forms like "1s" and "1.5s" every tick. The changing
width shifted everything after it on the status line twice a second,
making it distracting and hard to read. Render one fixed decimal below
the minute mark ("1.0s", "1.5s"); the >=1m path is unchanged.

Fixes #6402

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(cli): cover the 0s timer-start frame in the fixed-width time test

Review follow-up: useTimer initializes and resets at exactly 0, so
assert the "(0.0s · esc to cancel)" frame too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 14:22:05 +00:00
Shaojin Wen
7281f0de8c
feat(core): add working_dir to the Agent tool for pinning subagents to an existing worktree (#6456)
* feat(core): add working_dir to the Agent tool for pinning subagents to an existing worktree

Add an opt-in working_dir parameter to the Agent (sub-agent) tool that pins a sub-agent's entire working-directory context to an existing, caller-owned git worktree of the current repository. Unlike isolation: 'worktree', the harness neither provisions nor removes the directory — the caller owns its lifecycle — so a sub-agent can be aimed at a worktree that some earlier step already prepared.

Every "where am I?" surface on the sub-agent's config (target dir, cwd, project root, file discovery, workspace context) is rebound to the worktree, reusing the existing worktree-isolation rebind, so the sub-agent's file and shell tools operate inside that directory and cannot leak into the parent project tree. The path is validated as a worktree registered against the current repository before use, and working_dir is mutually exclusive with isolation.

Wire the /review skill to pass working_dir to every review agent for same-repo PR reviews, so the PR worktree isolation is enforced deterministically at the agent boundary instead of by prompt convention. This makes reviewing multiple PRs concurrently safe.

* fix(core): harden working_dir validation from review feedback

- Reject a working_dir that resolves to the repository's own main working tree. getRegisteredWorktreeBranch accepts the primary checkout too, so `working_dir: "."` or the repo root would otherwise pin a sub-agent to the user's main tree and silently defeat the isolation. A linked worktree has a `.git` file; the main tree has a `.git` directory — require the former.
- Reject working_dir combined with run_in_background. The caller owns the worktree lifecycle and could remove it while a detached agent is still running in it (ENOENT on its own cwd); the isolation: 'worktree' path does not have this problem because the tool owns and reaps the worktree.
- Add an isGitRepository() preflight so a non-repo parent reports the real cause instead of a misleading "not a registered git worktree" error.
- Add tests: repo-relative working_dir resolution (the /review production form), main-working-tree rejection, and the run_in_background guard.

* fix(core): make main-worktree detection robust and tighten working_dir validation

Address a second review round on the Agent tool working_dir parameter.

- Replace the ".git is a file ⟹ linked worktree" heuristic with a reliable check: a linked worktree's per-worktree git dir (--absolute-git-dir) differs from the common git dir (--git-common-dir), while a main working tree's are identical. The heuristic was wrong for a main tree whose .git is a file (git clone --separate-git-dir, submodules), which would have let the main checkout pass the isolation guard. Extracted as GitWorktreeService.isLinkedWorktree(); the fs.stat probe (and its all-errors catch) is gone.
- Reject whitespace-only working_dir (trim before the empty check), matching the worktree-name validation.
- Tests: assert the sub-agent's getCwd()/getWorkingDir() are rebound (not just project root / target dir); cover the monorepo-subdirectory re-anchor branch (getTargetDir() below the repo root); and add real-git coverage for isLinkedWorktree, including the separate-git-dir main tree.

* fix(core): reject working_dir for named teammates

A named teammate spawns via TeamManager with cwd = getCwd() and returns before the working_dir rebind is reached, so the pin was silently ignored and the teammate ran in the parent working tree — a false sense of isolation. Reject working_dir at the point a teammate would actually spawn (name + active TeamManager + top-level session), leaving the name-without-active-team fallback (which spawns a normal sub-agent, where working_dir does apply) untouched. Add a test.

* docs(core): describe working_dir as a cwd pin, not a sandbox

The working_dir parameter description, the interface doc, and the /review skill said the sub-agent "cannot touch"/"cannot leak into" the parent tree. That overstates it: file and shell tools still accept absolute paths, so the isolation is a working-directory pin (cwd-relative operations and search tools stay inside the worktree), not a filesystem sandbox — the same limitation as isolation: 'worktree'. Reword all three to state this accurately.

* test(core): cover working_dir non-git and fail-closed paths; clarify main-tree error

- Add an execute-level test for the working_dir isGitRepository() preflight (non-git parent directory -> "not a git repository").
- Add a test for isLinkedWorktree's fail-closed catch (a non-git path returns false).
- Reword the main-tree rejection error to name both possible causes ("is the main working tree, or its git metadata could not be read"), since isLinkedWorktree also fails closed on a git error rather than only on the main tree.

* fix(core): canonicalize path in isLinkedWorktree; test relative working_dir in a monorepo

- isLinkedWorktree now realpaths the worktree path before comparing --absolute-git-dir (which git returns canonicalized) against --git-common-dir. Without this, a symlinked input (macOS /var → /private/var, os.tmpdir()) made the two diverge for the main working tree and misreported it as linked, letting the main checkout pass the isolation gate.
- Add a test proving a repo-relative working_dir resolves against the subdirectory cwd where fetch-pr actually creates the worktree (git resolves relative worktree paths against cwd), not the repo root — the production monorepo form.

* fix(core): close working_dir gaps from review — classifier, background config, review-workflow coverage

- Include working_dir in toAutoClassifierInput so AUTO-mode permission classification can see the child is rebound to another worktree; a launch could otherwise look benign from subagent_type + prompt alone.
- Reject working_dir when the effective background decision is true — not only for the explicit run_in_background param (already rejected in validateToolParams) but also for a subagent config with background: true, which validateToolParams cannot see. Guard on the resolved shouldRunInBackground so a nested call that downgrades to the foreground is not over-rejected.
- /review skill: extend the working_dir mandate to the Step 4 verification agent and Step 5 reverse-audit agents, which also read files and re-check the diff and would otherwise run against the user's main checkout.
- Add tests for all three.

* test(core): symlink + preserved-suffix coverage; soften working_dir grep/glob wording

- isLinkedWorktree: add a test that passes a symlinked path (the macOS /var → /private/var case, reproduced with an explicit symlink) so the realpath canonicalization is actually exercised.
- Assert a caller-owned worktree run does not emit a spurious "[worktree preserved]" suffix, guarding the externallyManaged cleanup skip against regression.
- Soften the working_dir description: grep/glob default to the worktree as their root but can still be pointed outside via an explicit path, so drop the "enforce it as the workspace boundary" claim.

* fix(core): validate working_dir against the authoritative git worktree registry

- Replace the --git-dir vs --git-common-dir "is it a linked worktree" heuristic with an authoritative check against `git worktree list`. The heuristic could be fooled by a hand-crafted directory carrying a .git file copied from a real linked worktree (structurally a linked worktree, yet not registered); `git worktree list` reads .git/worktrees/<name>/gitdir and excludes such a fake. Renamed isLinkedWorktree -> isRegisteredLinkedWorktree.
- Tests: reject a fake worktree (copied .git file); match a registered worktree through a symlinked input path; and a nested-context test confirming a background: true subagent that downgrades to foreground is NOT rejected by the working_dir background guard (guards the shouldRunInBackground keying against regression).

* fix(core): fix flaky nested working_dir test; harden worktree-registry parsing

- The nested background:true test relied on the default '/test/project' cwd, which does not exist on CI, so simple-git threw at construction instead of the test reaching the isGitRepository() path (this is the red CI on the prior commit). Point it at a real, non-git temp directory instead.
- isRegisteredLinkedWorktree: parse `git worktree list --porcelain -z` (NUL-terminated) so a worktree path that itself contains a newline cannot inject a fake `worktree <path>` entry into a newline-split parse; and reject the primary working tree by canonical path so a linked record whose on-disk path is a symlink onto the main checkout cannot smuggle it through.

* fix(core): fall back when `worktree list -z` is unsupported; add newline-injection regression test

- `git worktree list -z` requires Git >= 2.36. On older git the call threw and the surrounding catch rejected every valid caller-owned worktree with a generic "not a registered linked worktree" error. Fall back to the newline-separated porcelain form instead of hard-failing: that parse is only vulnerable to a worktree path that itself contains a newline, which is a far narrower exposure than rejecting every worktree.
- Add a real-git test that creates a worktree whose path embeds "\nworktree <other>" and asserts the injected path is not accepted while the real (awkwardly named) worktree still validates. Confirmed the test fails when the parser reverts to newline splitting, so it genuinely guards the -z parse.

* fix(core): accept detached-HEAD worktrees for working_dir; cover the failure-path cleanup guard

- getRegisteredWorktreeBranch returns null for a detached-HEAD worktree (its branch reads as 'HEAD'), so a legitimate `git worktree add --detach` target was rejected with a factually wrong "not a registered git worktree" error and the authoritative registry check never ran. Make isRegisteredLinkedWorktree the sole gate and read the branch best-effort: branch is unused for caller-owned worktrees anyway, since cleanup short-circuits on externallyManaged.
- Add a test asserting a detached worktree is accepted and the sub-agent is rebound onto it.
- Add a failure-path test: when the sub-agent throws, the finally / outer-catch path runs cleanupWorktreeIsolation() and the externallyManaged guard must leave the caller's worktree intact. Confirmed both new tests fail without their respective fixes, so they genuinely guard the behaviour.

* fix(core): verify a worktree's registry pointer instead of parsing `git worktree list`

Parsing the registry list forced a bad trade: `--porcelain` alone is newline-delimited, so a worktree path containing a newline can inject a bogus entry, while `-z` needs Git >= 2.36 and would reject every worktree on older git. Verify the single path in question instead:

1. `--git-dir` equal to `--git-common-dir` means the primary working tree, which is rejected.
2. The common dir must be this repository's, or the path belongs to another repo.
3. `<gitDir>/gitdir` records the worktree that registry entry belongs to; it must resolve back to the probed path.

Step 3 is what makes it authoritative: a directory holding a `.git` file copied from a real linked worktree does report a per-worktree git dir, but that entry's pointer names the real worktree, not the copy. No list is parsed, so the newline-injection class of bug is gone on every git version and no `-z` (Git >= 2.36) is required. Detached worktrees, symlinked inputs, separate-git-dir main trees, and other repositories' worktrees are all still handled correctly.

Also bump the real-git integration suite's vitest timeouts to 30s, matching the sibling hooks/symlinks suites, to reduce CI flakiness.

* fix(core): contain working_dir to the repo, tailor the pinned-worktree notice, keep the newline test off Windows

- Containment: a registered worktree can live anywhere on disk, and pinning rebinds the child's WorkspaceContext wholesale, so a model-supplied path could silently move the file tools' boundary outside the repository. Require the resolved path to sit inside the repo (canonical comparison, so a symlink cannot straddle the boundary) and say so in the parameter description. isolation: 'worktree' already had this implicitly.
- Prompt: a caller-owned worktree is the code the agent was asked to work on, not a provisioned copy of the parent's tree. Give it a narrow notice instead of the isolation notice, whose "translate the parent's paths" and "re-read what the parent changed" guidance contradicts the caller's own instructions (/review tells its agents not to cd or prefix absolute paths).
- Skip the newline-injection test on Windows: a path component containing a newline is not representable there (the embedded drive letter makes it invalid), so `git worktree add` errors and the windows test job would fail. The injection cannot occur on Win32 either.
- Add a regression test for a stale registry record whose directory was recreated as a plain directory: `git worktree list` keeps such records for months, and `git rev-parse --show-toplevel` inside one resolves to the MAIN checkout. Probing the path itself rejects it.

* docs(core): correct the working_dir helper's doc comment

The comment still credited getRegisteredWorktreeBranch with enforcing "must be a registered worktree". That gate is now isRegisteredLinkedWorktree (plus the in-repository containment check); getRegisteredWorktreeBranch is consulted only for a best-effort branch label and is deliberately not a gate, since it returns null for a legitimate detached-HEAD worktree.

* fix(core): read the worktree registry from the repo, not the candidate directory

isRegisteredLinkedWorktree derived everything from files inside the directory being validated: `<target>/.git` names a git dir, whose `commondir` and `gitdir` were then trusted. All three are candidate-controlled, so a *fabricated* chain (not merely a `.git` copied from a real worktree) could name itself and be accepted even though git's registry holds no entry for it. The docstring's claim that it consulted "git's own registry entry" was therefore wrong.

Read the registry from the repository instead: some `<commonDir>/worktrees/<name>/gitdir` must name the path. Keep a liveness probe inside the path as well: its own `--git-dir` must be that same entry. The two are complementary. The registry answers "is this path registered?" and so rejects a fabricated chain, a copied `.git` file (the entry names the original), another repository's worktree, and the primary working tree, which has no entry of its own. Only the probe answers "is it a worktree right now?" and so rejects a stale `prunable` record whose directory was deleted and recreated as an ordinary directory.

Add a fabricated-chain regression test, and confirm each of the two tests fails when its corresponding half of the check is removed.
2026-07-08 13:45:19 +00:00
Copilot
e7f94a5a3c
fix(core): detect non-SSE HTTP 200 responses in OpenAI streaming pipeline (#6466)
* Initial plan

* fix(core): detect non-SSE HTTP 200 responses in OpenAI streaming pipeline

When a gateway/proxy intercepts a streaming request and returns HTTP 200
with a non-SSE content-type (e.g. text/html), the OpenAI SDK silently
produces zero chunks, resulting in a confusing "Model stream ended without
a finish reason" error and an empty interaction log (response: null,
error: null).

Add NonSSEResponseError that is thrown early when the response content-type
is incompatible with SSE (not text/event-stream, application/json, etc.).
The error carries diagnostic metadata: HTTP status, content-type, bounded
body prefix, and request-id header — allowing users and maintainers to
distinguish "empty model stream" from "gateway returned non-SSE page".

Uses the OpenAI SDK's withResponse() API to access the raw HTTP response
headers before consuming the stream iterator. Falls back gracefully when
withResponse() is unavailable (e.g. in mocked environments).

Closes QwenLM/qwen-code#6465

* fix(core): replace non-null assertion with nullish coalescing in content-type check

* fix(core): address non-sse review feedback

* fix(core): expose non-sse request id alias

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yiliang114 <effortyiliang@gmail.com>
2026-07-08 12:06:32 +00:00
ytahdn
b2b02d27ff
feat(web-shell): expose external split controls (#6523)
* feat(web-shell): expose external split controls

* fix(web-shell): tighten split controlled behavior

* fix(web-shell): address split control review

* fix(web-shell): sync controlled split exit

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-07-08 12:02:10 +00:00
ChengHui Chen
1f92787aa0
feat(channels): add dmPolicy config to disable private/DM messages (#6521)
* feat(channels): add dmPolicy config to disable private/DM messages

Add DmGate class mirroring GroupGate to gate DM/private messages in
channel adapters. Operators can now set dmPolicy: 'disabled' in their
channel config to silently drop all DM messages while keeping group
messages active.

Closes #6392

* fix(channels): address review feedback for dmPolicy

- Add dmPolicy: 'open' to all test config factories (8 files) to
  maintain type correctness with required ChannelConfig field
- Add integration tests in ChannelBase.test.ts:
  - preflightInbound: DM dropped + group passes when dmPolicy=disabled
  - isStoredLoopTargetAuthorized: DM loop job disabled + group passes
- Add dmPolicy assertions in config-utils.test.ts (default + explicit)
- Keep dmPolicy as required field (not optional) for strict parity
  with groupPolicy
2026-07-08 12:01:10 +00:00
Zqc
151d269413
feat: extension file reload — watch for plugin changes and hot-reload runtime (#6347)
* feat: extension file reload — watch for plugin changes and hot-reload runtime

- Extract refreshExtensionRuntime to centralize MCP, skills, subagents, hooks, and memory refresh
- Add ExtensionFileWatcher (chokidar) for auto-detecting extension file changes
- Add ExtensionRefreshState with per-session scoped instance and mutation suppression
- Replace monkey-patching with ExtensionManager native mutation listeners
- Add /reload-plugins slash command with i18n-aware summary across all 9 locales
- Add auto-refresh of extension content (commands/skills/agents) on file change
- Add HookRegistry.reloadConfiguredHooks() with correct error recovery
- Fix async mutation pairing via id-based Map instead of LIFO stack
- Fix bootstrap watcher close() UB with queueMicrotask deferral
- Fix concurrent refresh with runningRef/pendingRef guard
- Fix error propagation from refreshExtensionContentRuntime to UI
- Fix isIgnored cross-platform path splitting (path.sep → regex)
- Fix wrong ExtensionMutationEvent type via import from core
- Fix addItem on unmounted component with mountedRef guard
- Set followSymlinks: false on chokidar watchers

* fix: address extension reload review feedback

* docs: expand extension file reload design

* fix: harden extension reload watcher state

* fix(core): tag extension refresh legs

* fix(cli): harden extension reload state handling

* fix(cli): clarify extension reload failure state

* fix(cli): tighten extension reload boundaries

* chore: resolve main conflicts for extension reload

* chore: drop unrelated merge formatting changes

* fix(core): harden extension refresh edge cases

---------

Co-authored-by: 俊良 <zzj542558@alibaba-inc.com>
Co-authored-by: 易良 <1204183885@qq.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-08 11:16:21 +00:00
Shaojin Wen
271664b34b
feat(cli): auto-retry next port when serve port is in use (#6513)
* feat(cli): auto-retry next port when serve port is in use

When `qwen serve` or `npm run dev:daemon` encounters EADDRINUSE on the
default port (4170), automatically try the next available port (up to 10
attempts) instead of failing immediately. This allows running multiple
daemon instances side-by-side without manual port management.

- run-qwen-serve.ts: replace single listen() with recursive tryListen()
  that retries on EADDRINUSE; --port 0 (ephemeral) skips retry
- daemon-dev.js: pre-scan for an available port via net probe before
  spawning the daemon child, ensuring health-poll and web-shell target
  the correct URL
- Tests: retry-then-succeed, non-EADDRINUSE immediate-fail, and existing
  all-ports-exhausted test all pass (138 tests)

* test(cli): fix EADDRINUSE mock to create fresh server per listen attempt

The existing test reused a single fakeServer across all retry attempts,
accumulating 10+ once('listening') listeners and triggering a
MaxListenersExceededWarning. Create a new server per call to match
production behavior where each tryListen creates a fresh server.

* fix: address review feedback on port retry

- daemon-dev.js: strip IPv6 brackets before probe (ENOTFOUND fix),
  add port range/NaN validation
- run-qwen-serve.ts: remove duplicate runtime error listener
  (onListening already installs one via removeAllListeners + on)
- tests: add exhaustion (all 10 ports), port 0 EADDRINUSE no-retry,
  and stderr retry message assertion (140 tests pass)

* fix: update stale comment referencing removed server.once('error', reject)

* fix: address R2 review — port cap, TLS reuse, exhaustion summary

- daemon-dev.js: cap probe at port 65535, skip probe when user
  specifies --port, add --compacted-replay-max-bytes to whitelist
- run-qwen-serve.ts: move https.createServer before tryListen (avoid
  recreating TLS context per retry), cap retry at 65535, log summary
  "all ports X–Y are in use" on exhaustion
- tests: verify exhaustion summary stderr message

* fix: clear stale listening listeners on httpsServer before retry
2026-07-08 10:31:52 +00:00
qwen-code-dev-bot
c516ee8c2f
fix(core): omit deprecated temperature param for Claude 4.8+ (#6520)
* fix(core): omit deprecated temperature param for Claude 4.8+

Claude Opus 4.8 deprecated the `temperature` sampling parameter —
the server returns 400 with "temperature is deprecated for this model"
when it is sent.

Add `modelRejectsTemperature()` version gate (mirroring the existing
`modelRejectsManualThinking()` pattern) and conditionally omit
`temperature` from the request body for models with major > 4 or
4.minor >= 8. Older models and unknown/unversioned ids retain the
previous behavior (default temperature of 1).

Fixes #6519

* fix(core): address review — ternary form, debug warn, 5.x + unknown tests

- Switch from boolean-spread to ternary form for idiom consistency
  with the thinking/output_config spreads at L697-698.
- Add a once-per-generator debugLogger.warn when a user-configured
  temperature is silently dropped on 4.8+ (latched like effortClampWarned).
- Add test coverage for claude-sonnet-5 (5.x family omits temperature)
  and unknown/unversioned model id (keeps temperature) to pin the
  major > 4 branch and prevent future narrowing.

---------

Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com>
2026-07-08 10:13:42 +00:00
jinye
43e6a9300a
feat(cli): Enable multi-workspace session routing (#6511)
* feat(cli): Enable multi-workspace session routing

Implement the Phase 2a sessions closed loop for qwen serve multi-workspace mode. Multiple explicit workspaces now create registered runtimes while legacy workspace surfaces remain primary-only, and live session routes dispatch by owning runtime.

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

* fix(cli): address phase2a session review feedback

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

* fix(cli): cover remaining phase2a review gaps

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

* fix(cli): address phase2a session review feedback

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

* fix(cli): satisfy phase2a lint checks

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

* fix(cli): align multi-workspace status test limits

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

* fix(cli): address phase2a session review feedback

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

* codex: address PR review feedback (#6511)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-08 10:06:31 +00:00
callmeYe
a07fdc6042
fix(memory): allow forget to remove user managed memory (#6432)
* fix(memory): allow forget to remove user managed memory

* fix(memory): harden forget index rebuilds

* test(cli): stabilize session archive race assertion

* test(memory): cover deny precedence with ask bypass
2026-07-08 09:51:36 +00:00
Shaojin Wen
e28a6371df
fix(cli): allow approval-mode changes without bearer token (#6527)
POST /session/:id/approval-mode was gated with `mutate({ strict: true })`,
requiring a bearer token even though similar session-scoped routes like
POST /session/:id/model and POST /session/:id/language use `mutate()`
(non-strict). This prevented the Web Shell from setting YOLO approval
mode on daemons without a configured token, showing a confusing toast
error on every new YOLO session.

Relax the approval-mode route to `mutate()` to match the model route,
consistent with the session create/load/resume lifecycle paths that are
also non-strict.
2026-07-08 09:43:49 +00:00
DennisYu07
a6ad81f23c
feat(hooks): inject background tasks and cron jobs status into Stop/SubagentStop hook payloads (#6531)
Extend StopInput and SubagentStopInput with background_tasks and crons fields,
and wire existing BackgroundTaskRegistry and CronScheduler snapshots into
fireStopEvent/fireSubagentStopEvent. This enables hook scripts to observe
async work state at agent stop time for status reports, cleanup logic, and
monitoring.

Fields are always present (empty arrays when no active tasks/crons). Data
collection is non-blocking via try/catch to not delay stop hook response.

Closes #6529

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-08 09:39:06 +00:00
易良
51f0364d63
fix(cli): keep status line on session model (#6514)
* fix(cli): keep status line on session model

Resolve status line model display drift after fast-model background subagents.

Refs #6512

* test(cli): cover preset status model fallback
2026-07-08 09:26:18 +00:00
Shaojin Wen
8296ce9e54
fix(web-shell): i18n for ~43 hardcoded English strings across 15 files (#6516)
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(web-shell): i18n for ~43 hardcoded English strings across 15 files

Multi-round audit replaced hardcoded UI strings with t() calls and
added en/zh-CN i18n keys for:

- Session timeline labels, aria-labels, and kind labels (MessageList)
- SubAgent tab labels, tools count, pending/running status
- Auth protocol option labels, placeholders, API key masking
- Voice dictation UI states (VoiceButton)
- Copy tooltip, Runtime badge, N/A, Server/Agent fallbacks
- Mermaid code block labels, image alt text, model switch summary

* fix(web-shell): i18n for missed models placeholder in AuthMessage

* fix(web-shell): invalidate timeline cache on locale switch

Store the t function reference in sessionTimelineCache alongside the
message signature. When the locale changes, t gets a new reference,
triggering cache invalidation and re-generating entries with the new
language labels.
2026-07-08 08:46:37 +00:00
jifeng
5b2d1369b5
fix(web-shell): refine markdown table interactions (#6500)
* fix(web-shell): refine markdown table interactions

* fix(web-shell): preserve active column when closing filters
2026-07-08 08:37:56 +00:00
callmeYe
727c2d580c
fix(web-shell): prevent sidebar footer overflow (#6522) 2026-07-08 08:28:12 +00:00
Heyang Wang
880b06ed4d
fix(cli): clean up IDE client after deferred timeout (#6509)
Deferred IDE startup could report a timeout while the underlying connection
promise later completed, leaving internal IDE state inconsistent with the
visible startup failure state.

- Disconnect the IDE client when deferred startup connection times out
- Repeat cleanup if the original connection later succeeds after timeout
- Keep late rejection handling to avoid unhandled promise rejections
- Cover timeout cleanup, late success, and quick rejection cases

Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
2026-07-08 07:43:59 +00:00
MikeWang0316tw
5b43edcaf6
fix(cli): bound the live streaming-table pending height (fix scroll-to-top lock, stall-then-dump, header flash) (#6421)
* fix(cli): charge a streaming table's wrapped height so it doesn't jump

The rendered-height estimator charged a table `2 * dataRows + 5`, assuming
one line per data row. When cells wrap (a wide table — many columns, long
content — on a bounded content width), each row renders taller, so the live
frame briefly exceeds the viewport and ink repaints from the top (a jump to
the top; #6170's clip/commit recover it, so it does not lock, but the jump
is visible).

Charge each row (header + data) by its wrapped height instead: approximate
the column width as an equal share of the content area (TableRenderer
shrinks columns proportionally to fit; an equal share never gives a wide
cell more room than TableRenderer would, so it is a safe upper bound) and
sum the tallest wrapped cell per row plus the inter-row separators and
chrome. For a table that fits, every row is one line and the formula
reduces exactly to the previous `2 * dataRows + 5`.

Only the height estimate changes (shared by the render-side clip and the
incremental scrollback commit); no rendering behaviour changes.

28 estimator tests pass; MarkdownDisplay clip tests unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cli): charge a wide-terminal table's vertical fallback height

The pending-height estimator mirrored only TableRenderer's WIDTH-based
vertical-fallback trigger, not the maxRowLines one. On a wide terminal a
multi-column table whose cells wrap past MAX_ROW_LINES is laid out
vertically (label: value — much taller), but the estimator charged the
shorter horizontal height, so the live frame overflowed and Ink fell into
its from-top full-redraw path (the scroll-to-top lock).

Model both triggers: compute the tallest wrapped cell (maxRowLines) in the
same pass as the horizontal height, and when it exceeds MAX_ROW_LINES
charge the vertical height instead. The vertical estimate now also wraps
each label:value at contentWidth so a long value that wraps is not
under-counted. maxRowLines uses an equal column share (never wider than
TableRenderer's column), so it is an upper bound and a real vertical
table is never missed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cli): cap the live pending region at the viewport height (non-VP)

The estimator's source-line slice is the primary bound on the live
(non-<Static>) pending frame, but it is disabled whenever
availableTerminalHeight is undefined — which is exactly what happens when
constrainHeight is off (ctrl-s "show more lines"). A tall pending item, e.g.
a long vertical-fallback table, then renders past the viewport; Ink cannot
update incrementally, clears the terminal and redraws from the top on every
repaint — the scroll-to-top lock.

Wrap the non-VP pending region in an Ink maxHeight={availableTerminalHeight}
overflow="hidden" box as a hard backstop. availableTerminalHeight already
excludes the footer/controls, so the live frame can never exceed the
viewport and Ink never trips clearTerminal. While constrained the estimator
keeps content well under this, so the clamp is inert and only engages on
residual overflow. ShowMoreLines stays outside the clamp (it renders only
while constrained, so the clamp is inert then, and must not be clipped).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cli): anchor the estimator's vertical trigger to the first row

Mirror TableRenderer's change to decide the horizontal-vs-vertical format
from the header + first data row only (not every row), so the estimator and
the renderer still agree on which format a streaming table uses. Row heights
above continue to sum every row; only the maxRowLines trigger is anchored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cli): commit a completed table that alone fills the pending budget

The rendered-height-aware incremental commit stalled when a single block
(a long-text table modelled tall / vertical) charged more than the commit
budget on its own. fitPendingSlice returns kept = the block's trailing
blank line, so the safe split boundary sits exactly at keptLines, but the
boundary search started at keptLines - 1 and missed it. The table has no
internal blank line and the blank before it was already committed, so the
search found nothing and broke the loop. Every later block then appended
past keptLines, so the search window never again contained a blank line —
nothing committed until the stream finalized and dumped all remaining
tables at once (the "stream a few tables, pause, then dump the rest" bug).

Start the boundary search at keptLines so a completed over-tall block's
trailing blank is found and the block commits to <Static>. Committing an
over-tall completed block is fine — only the live pending frame must stay
within the viewport.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cli): don't flash the header while its separator streams in

The forming-table hold-back released the header the moment its separator
line ended with `|` at a column count different from the header — meant to
let a genuinely mismatched separator render as plain text. But a separator
is typed one group at a time and momentarily ends with `|` at every
intermediate count (`| --- | --- |` on the way to seven columns), so the
header flashed as raw `| … |` text on every closed-group frame while the
separator streamed in — a visible strobe for wide (7-column) tables.

A streaming separator only ever gains columns, so treat a mismatch as final
only when it can no longer become valid: it overshot the header's column
count, or a further line has already committed it (it is not the trailing
line). Also hold the header while the separator is still a bare-pipe prefix
(`|`, `| `) before its first dash. The only remaining flash is the
unavoidable one-cell header window (`| Foo |`), indistinguishable from a
single-pipe line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test: add a terminal-capture ratchet for the streaming-table scroll-to-top lock

Drives ten wide 7-column tables with ~200-char wrapping cells through the
real TUI via a chunked fake OpenAI server and counts the full-screen clears
(`\x1b[2J\x1b[3J\x1b[H`) the app emits while they stream. Each such clear
resets the terminal scroll position, so it is exactly the "jump to top" a
user hits when scrolling up mid-stream. With the pending-height estimator
fix the count is 0; without it the under-charged frame overflows and the
count is ~300. Ratchet fails if it exceeds 20.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cli): tighten the streaming-table vertical height estimate (review)

Two accuracy fixes to fitPendingSlice from PR review, both reducing residual
under-charge of the vertical fallback:

- Charge each vertical data cell as its rendered `label: value` line (parsing
  the header labels once), not the value alone — TableRenderer's
  renderVerticalFormat prefixes the header label, so a long label pushed the
  wrapped line count higher than the estimator accounted for.
- Only model the vertical layout once a data row exists (`dataRows > 0`). With
  just a header + separator, TableRenderer keeps the horizontal header box, so
  the estimator no longer charges the shorter 2-row vertical stub for that
  transient state on a narrow terminal.

Adds a test where the `label: value` line itself wraps (covering the wrapped,
label-inclusive formula) and a zero-data-row narrow-terminal test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cli): correct the clamp margin and share MAX_ROW_LINES (review round 2)

- Charge tableClampRows + 2 when the clamp engages: TableRenderer wraps the
  height-clamped <Text> in <Box marginY={1}>, so a clamped table renders two
  margin rows beyond maxHeight that the estimator was dropping.
- Make TABLE_MAX_ROW_LINES the single source of truth (pending-rendered-height)
  and import it into TableRenderer as MAX_ROW_LINES, so the renderer and the
  estimator can never disagree on the wrap-to-vertical threshold. Direction is
  util→renderer so the pure height module stays free of the React/ink graph.
- Correct the perColWidth comment: an equal column share is exact for uniform
  columns but can under-count a heterogeneous table (the renderer shrinks a
  narrow column below the share); the MainContent maxHeight backstop is the
  hard cap for that residual case.
- Add a horizontal-layout test whose cells wrap within MAX_ROW_LINES, covering
  the per-row wrapped contentRows sum (not just the old flat 2*dataRows).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cli): estimator cleanups and stronger tests (review round 3)

- Charge the clamped-table margin: covered by a new test asserting a clamped
  table costs tableClampRows + 2 (the marginY the earlier fix added).
- Strengthen the "no stall-then-dump" regression: require all three tables to
  have committed (>= 3 items, each table marker present), so a partial stall
  no longer passes.
- Reuse headerCells instead of re-parsing the header row inside the loop, drop
  the redundant .trim() (splitMarkdownTableRow already trims), and start the
  data-row loop at i + 2.
- Refresh the stale TABLE_CHROME_ROWS JSDoc (no longer 2*dataRows+5) and
  document the estimator's known under-charge gaps (proportional column widths,
  word-aware wrapping, the renderer's post-layout width fallback) that the
  MainContent maxHeight backstop is the hard cap for.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:33:55 +00:00
Shaojin Wen
d8dc8043d6
feat(web-shell): restore the full composer in split-view panes (#6510)
* feat(web-shell): restore the full composer in split-view panes

The in-window split view rendered each pane through a deliberately minimal composer: typing "/" opened nothing, and the approval-mode, model, and voice controls were all hidden. Wire each pane's composer to its own session so all four work per pane.

The slash menu lists the pane session's own daemon commands and is submitted through that session, where the daemon executes it server-side — so e.g. /clear clears that pane's session, not the outer chat. The approval-mode and model pickers drive the pane session's own setApprovalMode / setModel actions, and the SDK reflects the change back on the connection. The shared model-visibility filter moves to utils/composerModels so the main chat and split panes hide the same models.

* feat(web-shell): auto-approve pending tool call when a pane switches to yolo

Address review feedback on #6510: switching a split pane to yolo (or auto-edit for an edit tool) now auto-approves a tool call already awaiting approval in that pane — mirroring App's handleSetMode so the shortcut behaves the same as in the single-session chat. The pending approval is read through a ref so the async setApprovalMode resolution targets the approval current at that time, not a stale one.

Also cover the two untested branches of handleSelectMode: the isDaemonApprovalMode guard (invalid mode is rejected without hitting the daemon, reported via onError) and the setApprovalMode async-rejection path.
2026-07-08 07:01:36 +00:00
jinye
1420566620
feat(serve): Bound replay snapshot history (#6482)
* feat(serve): Bound replay snapshot history

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

* codex: address PR review feedback (#6482)

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

* codex: address PR review suggestions (#6482)

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

* test(acp-bridge): fix replay truncation assertion access

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

* fix(serve): keep replay cap validation out of fast path runtime

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

* fix(acp-bridge): reset replay window on bulk seed

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

* codex: address PR review feedback (#6482)

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

* codex: fix CI failure on PR #6482

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

* fix(sdk): expose bounded replay status types

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-08 06:53:58 +00:00
顾盼
5605c1b60d
fix(cua-driver): migrate Windows scripts + README rewrite (#6515)
* fix(cua-driver): migrate Windows install/uninstall scripts to qwen-cua-driver

install.ps1 and uninstall.ps1 were essentially un-migrated from upstream
trycua/cua — binary name was `cua-driver.exe` instead of
`qwen-cua-driver.exe`, repo pointed to trycua/cua, install paths used
upstream vendor names.

Changes:
- Binary: cua-driver.exe → qwen-cua-driver.exe
- Repo: trycua/cua → QwenLM/qwen-code
- Install dir: Programs\Cua\cua-driver\ → Programs\Qwen\qwen-cua-driver\
- Home dir: .cua-driver → .qwen-cua-driver
- Autostart task: cua-driver-serve → qwen-cua-driver-serve
- All URLs and user-facing text updated
- BAKED_VERSION: 0.6.8 → 0.7.0
- Added PATH restart hint after install
- Docs URL in post-install-hints.txt and _install-rust.sh fallback

* fix(cua-driver): correct cua-driver-uia.exe process name in install.ps1

The UIA helper binary ships as cua-driver-uia.exe in release tarballs
(not qwen-prefixed), so taskkill and Get-Process must use the actual
binary name.

* docs(cua-driver): rewrite README with full install/verify/MCP guide

Covers macOS, Windows, Linux installation; verification steps; MCP
configuration for Qwen Code, Claude Code, and Codex; relative-coordinate
mode; uninstall instructions; and platform support matrix.
2026-07-08 06:07:11 +00:00
AlexHuang
49aa4c8ab5
fix(cli): show file path in compact tool summary for single collapsible tools (#6448)
* fix(cli): show file path in compact tool summary for single collapsible tools

buildToolSummary() previously discarded the description field from
collapsible tools (ReadFile, Grep, Glob, ListFiles), showing only
generic counts like 'Read 1 file'. Now shows the actual file path or
search pattern for single tools, while preserving count format for
batches of multiple tools of the same type. Falls back to count format
when description is unavailable.

Signed-off-by: Alex <alex.tech.lab@outlook.com>

* fix(cli): sanitize description in buildToolSummary for error args and ANSI

When a tool call errors, useReactToolScheduler sets description to
JSON.stringify(args) which produces '{...}' blobs. Strip ANSI escape
sequences and reject JSON-looking descriptions so the summary falls
back to the count format instead of rendering raw JSON.

Signed-off-by: Alex <alex.tech.lab@outlook.com>

* fix(cli): broaden ANSI stripping and replace newlines with spaces in summary

Strip all common ANSI escape sequences (OSC, charset, CSI, single-byte
ESC) instead of just CSI. Replace all C0 control characters including
newlines with spaces so embedded \n in shell descriptions does not
break the single-line compact summary layout.

Signed-off-by: Alex <alex.tech.lab@outlook.com>

* test(cli): update ToolGroupMessage expectations for description-based summary

The buildToolSummary change from 'Read 1 file' to 'Read a.ts' broke
4 assertions in ToolGroupMessage.test.tsx. Update all 5 occurrences
to use the new description-based format.

Signed-off-by: Alex <alex.tech.lab@outlook.com>

---------

Signed-off-by: Alex <alex.tech.lab@outlook.com>
2026-07-08 05:26:47 +00:00
VectorPeak
e83d548cd9
fix(core): reject Windows-style workspace artifact paths (#6483)
Reject Windows-style absolute and traversal paths in record_artifact workspacePath validation by checking portable slash-normalized path semantics. This keeps artifact metadata aligned with the workspace-relative locator contract while preserving valid relative paths.

Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
2026-07-08 03:29:12 +00:00