Commit graph

5752 commits

Author SHA1 Message Date
易良
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
qqqys
58e51eb96c
fix(channel): Relay ACP permission requests (#6446)
* fix(channel): Relay ACP permission requests

* fix(channel): harden permission relay cleanup

* fix(channel): scope ACP permission approvals

* fix(channel): harden permission cancellation diagnostics

* test(channel): cover permission lookup edge cases

* fix(channel): close permission relay stale requests

* fix(channel): tighten approve-always option matching

* test(channel): cover permission relay cleanup gaps

* fix(channel): deliver threaded permission prompts

---------

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 03:27:25 +00:00
DennisYu07
79bc668b71
feat(cli): Show permission mode badge in footer for DEFAULT mode (#6498)
Always display the current permission/approval mode in the footer,
including when in the default (Ask permissions) mode. Previously,
non-default modes showed indicators but the default mode showed nothing,
creating ambiguity for users switching between modes.

- Add grey ⏸ badge with 'Ask permissions' text for DEFAULT mode
- Update both main Footer and AgentFooter to render DEFAULT indicator
- Use theme.text.secondary for subtle, unobtrusive styling
- Badge is i18n-aware using existing t('Ask permissions') key
- Other mode indicators remain unchanged

Closes #6496
2026-07-08 03:25:32 +00:00
Shaojin Wen
29cefd7fb1
fix(web-shell): count daemon sessions in Daemon Status usage dashboard (#6493)
* fix(web-shell): count daemon sessions in Daemon Status usage dashboard

The Web Shell usage dashboard read usage_record.jsonl exclusively, but only
the TUI /clear path ever writes that file — so daemon / Web Shell sessions and
any un-cleared session (whose usage lives only in the per-session transcripts)
were never counted. Real-world "today" totals could undercount ~20x.

Add core loadUsageHistoryWithLive(): the durable persisted history unioned with
a bounded replay of recent transcripts, deduped by sessionId (persisted wins,
as the authoritative final snapshot). rebuildFromSessionJsonl gains an mtime
window and a skip-set (read from each transcript's first line) so the merge is
incremental and cheap. The daemon /usage/dashboard route and loadUsageDashboard
now use it.

The trailing window (35d) keeps the summary + daily charts exact while bounding
load latency (~1.7s vs ~13s for a full-year replay on a heavy history); older
heatmap cells fall back to persisted data. With no persisted base (fresh
machine / pure Web Shell user) it replays the full history, so the heatmap is
never silently truncated. Read-only: serving the dashboard never writes ~/.qwen.

* fix(web-shell): address review — skip transcripts by filename, add coverage

Skip already-persisted transcripts by their filename (`{sessionId}.jsonl`,
guaranteed by chatRecordingService) instead of opening each file to read the
sessionId from its first line — zero I/O per skipped session on a cache miss.

Add tests: the loadUsageDashboard wiring counts a transcript-only (daemon)
session, the rebuilt-empty (all-persisted) common case, and a corrupt
usage_record.jsonl falling back to a full transcript replay.
2026-07-08 03:06:05 +00:00
Shaojin Wen
045bbee6ce
fix(web-shell): hide sidebar settings text when width is insufficient (#6494)
Prevent the 'Settings' label from wrapping to a new line when the
sidebar is narrow. Instead, the text is clipped via overflow:hidden
and only the gear icon remains visible.
2026-07-08 02:41:41 +00:00
顾盼
14f02c132a
fix(core): Match hook display-name matchers to tool ids (#6373)
* fix(core): match hook tool display names

* fix(core): tighten hook matcher aliases

* fix(core): align hook matcher aliases
2026-07-08 02:22:55 +00:00
VectorPeak
faf7c434fc
fix(core): reject fractional LSP limit inputs (#6455)
* fix(lsp): validate limit as positive integer

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

* fix(lsp): declare limit minimum in schema

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-08 01:41:14 +00:00
tanzhenxin
560e6103a9
feat(cli): review auto-generated skills with an inline preview, editor handoff, and an in-dialog off switch (#6393)
* feat(cli): skill review dialog — inline preview, open-in-editor, turn-off option

The auto-skill review dialog now shows the staged SKILL.md inline
(sanitized, bounded reads, wrap-aware height cap), opens it in the
configured editor without advancing (with watcher-based preview refresh
so non-blocking GUI editors work), and offers a visible last option to
turn the feature off — effective immediately in-session, persisted at
workspace scope, non-destructive to the pending batch. Bulk options
render only while at least two skills remain. Re-enabling auto-skill
from /memory can resurface a batch put aside by turn-off.

* test(integration): render harness + capture scenarios for the skill review dialog

Browser-free harness that renders the production dialog from source via
an ESM loader hook; its before mode renders the globally installed qwen
(no local fixture — the baseline is what actually shipped, or a loud
failure). Terminal-capture scenarios produce the PR's before/after
screenshots.

* fix(cli): address review findings on the skill review dialog

- Sanitize the model-generated name and description in the dialog header,
  same as the preview body — an escape sequence in the frontmatter must not
  reach the terminal through the header fields.
- Clamp the preview width to the dialog container cap (min(columns-4, 100),
  the same clamp DiffDialog uses) instead of the raw terminal width, which
  broke the wrapped-row accounting on terminals wider than ~106 columns.
- Catch settings persistence failures in the turn-off option: surface the
  error in the dialog and leave the feature untouched instead of letting the
  throw escape the keypress handler.
- Extract the auto-open gate into shouldAutoOpenSkillReview and cover it
  with a truth table (turn-off, /memory overlap, re-enable, Esc-dismiss).
- Cover the MemoryDialog auto-skill ON->OFF toggle direction.
- Release the capture harness temp dir with try/finally.

* fix(cli): guard the preview watcher against async errors and event bursts

An FSWatcher 'error' event after attach had no listener, so Node raised
it as an uncaught exception and the global handler exited the CLI.
Consume it and drop the watcher; the blocking-editor reload still works.

Also debounce the watch callback (300ms, same as SettingsWatcher): a
single editor save fires several raw events, and each one re-read the
file and re-attached the watcher.

* test(cli): drop white-box watcher tests, keep the end-to-end refresh test

The prototype-spy scaffolding tested implementation details (listener
registration, synthetic event bursts) and leaned on vite-node interop
quirks. The existing on-disk refresh test already exercises the watcher
path, debounce included.

* fix(cli): sanitize action errors, log preview read failures, cover key guards

- Render actionError through sanitizeMultilineForDisplay: error messages
  can embed the staged path, whose basename derives from the
  model-generated skill name.
- Log the underlying cause when the preview read fails; all failures
  render the same 'Preview unavailable' otherwise.
- Cover Ctrl+O/Cmd+O inertness and Esc dismissal with tests.
- Document that getAutoSkillEnabled() also gates on bare/safe mode.

---------

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 01:36:11 +00:00
jinye
27f8f2c95d
feat(cli): Add serve env isolation and total admission (#6416)
* feat(cli): add serve env isolation and total admission

Add runtime-local serve env snapshots, explicit env injection for low-cost workspace-scoped consumers, and sourceEnv support for ACP child spawn.

Add a daemon-wide maxTotalSessions admission reservation hook for fresh session creation while keeping multi-workspace sessions gated.

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

* codex: address PR review feedback (#6416)

Reject fractional maxTotalSessions values so the daemon-wide session cap remains an integer count and matches the documented limit semantics.

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

* fix(cli): address PR review feedback (#6416)

Always pass the runtime env to A2UI stdio transports, keep daemon runtime env metadata coherent after env reload fallback, and tighten total-admission coverage.

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

* fix(cli): address total admission review feedback (#6416)

Add retryable ACP error data for total session limits, log total-admission REST rejections, and keep session-limit response scopes explicit.

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

* fix(cli): address env review feedback (#6416)

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

* fix(cli): restore scheduled task serve deps (#6416)

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

* fix(cli): isolate runtime env reload base (#6416)

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

* codex: address PR review feedback (#6416)

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

* codex: address PR review feedback (#6416)

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

* codex: address PR review feedback (#6416)

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

* codex: address PR review feedback (#6416)

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

* codex: fix CI failure on PR #6416

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

* fix(cli): address daemon admission review feedback

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

* fix(cli): address runtime env review feedback

Scrub daemon bearer tokens from A2UI stdio MCP environments and prune reload-owned keys from the daemon runtime base before rebuilding runtime env snapshots.

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

* fix(cli): preserve daemon env base on reload

Keep runtime env rebuilds anchored to the boot-time daemon base snapshot, preventing reload-owned key pruning from dropping valid shell-exported values. Also carry env file read failure details into runtime metadata and daemon logs.

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

* fix(cli): satisfy env metadata lint rules

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-08 00:52:36 +00:00
qwen-code-ci-bot
86ae16a6d6
chore(release): v0.19.7 (#6484)
* chore(release): v0.19.7

* docs(changelog): sync for v0.19.7

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-07 17:25:48 +00:00
Shaojin Wen
65c82bed66
feat(web-shell): unify scheduled task sessions — bind chat-created tasks + clock icon (#6453)
* fix(web-shell): rename scheduled tasks "查看历史" to "查看对话"

* feat(serve): bind cron_create durable tasks to dedicated sessions via keepalive

The cron_create tool (core layer) writes durable tasks to disk without a
sessionId because it has no access to the session bridge. The keepalive
loop runs in the daemon process where the bridge IS available, so it
retroactively binds unbound tasks to dedicated sessions — the same flow
POST /scheduled-tasks uses for UI-created tasks. Each unbound task gets:
spawnOrAttach(sessionScope:'thread'), named  prompt, sessionId written
back to disk. This makes chat-created tasks show "查看对话" with a clock
icon in the session list, matching the UI's "新建定时任务".

* feat(serve): watch tasks file for immediate binding of new cron_create tasks

The keepalive interval is 2-5 minutes, so a chat-created task could wait
that long before being bound to a dedicated session — showing no "查看对话"
link until the next tick. Adding a file watcher (same directory-watch +
debounce pattern the scheduler uses) triggers an immediate tick when
cron_create writes to disk, so the task is bound within ~500ms.

* feat(serve): bind cron_create tasks to current session +  rename via keepalive

Switch from creating a separate dedicated session to binding the task to
the current chat session (so the first message is already in the
transcript). The keepalive then renames that session to  prompt — the
core layer can't rename sessions (no bridge access), but the daemon
process can. A Set tracks renamed sessions to avoid repeated
updateMetadata calls. Unbound tasks (legacy/CLI) still get new sessions
via the existing bind path.

* fix(core): keep createDurable() tasks unbound by default

Reverts the auto-binding of durable tasks to the current session in
createDurable(). Binding to a specific session means only that session
can fire the task (#shouldFireDurable), but non-daemon paths (TUI, ACP,
headless) have no keepalive to rehydrate the session after exit — making
tool-created durable tasks go dormant.

The daemon keepalive (bindAndNameSessions) already handles binding
unbound tasks to dedicated sessions with  naming, so daemon-mode
tasks get the same UX without the regression.

* fix(serve): roll back orphan sessions in keepalive binding + add tests

When bindAndNameSessions spawns a dedicated session for an unbound task
but the subsequent updateCronTasks write fails (or the task was deleted
between read and write), the spawned session was left behind with no
owning task — the next tick would see the task still unbound (or spawn
more orphans). Add rollback: closeSession + removeSession on failure,
matching the POST /scheduled-tasks rollback pattern.

Also add positive test coverage for the new binding paths:
- unbound task → spawn + name + write sessionId to disk
- bound task without  prefix → named exactly once (renamed Set dedup)
- task vanishes before write → spawned session is rolled back

* fix(serve): add timeout to spawnOrAttach in keepalive binding + test hardening

BZ-D: spawnOrAttach in bindAndNameSessions had no timeout boundary — a
hung spawn would keep running=true and stall all subsequent ticks,
stopping heartbeats/revives for every scheduled-task session. Wrap with
withTimeout (configurable via spawnTimeoutMs, default 30s) and attach a
background handler to clean up late-resolved orphans.

Also generalized withTimeout error messages to include the operation
name, and made spawn timeout configurable for tests.

Test improvements (GPT-5 review suggestions):
- Assert spawnOrAttach payload (workspaceCwd + sessionScope: thread)
- Verify SessionService.removeSession called during rollback
- Regression test: createDurable stays unbound after enableDurable
- Hung-spawn test: tick completes despite non-abortable spawn hang

* fix(serve): keepalive hardening + i18n sync (review suggestions)

- i18n: sync English 'View history' → 'View conversation' to match
  Chinese '查看对话'
- Prune renamed Set alongside reviveState when tasks are removed
- fs.watch: clarify null filename handling for Linux (treat as match)
- updateCronTasks: skip .map() when task not found (no-op optimization)
- Add tests: disabled unbound exclusion, naming failure resilience
2026-07-07 16:29:10 +00:00
Heyang Wang
3d1122d284
perf(cli): defer startup prefetch tasks (#6303)
Some checks failed
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
SDK Python / Classify PR (push) Has been cancelled
SDK Python / SDK Python (3.10) (push) Has been cancelled
SDK Python / SDK Python (3.11) (push) Has been cancelled
SDK Python / SDK Python (3.12) (push) Has been cancelled
* perf(cli): defer startup prefetch tasks

* fix(cli): await IDE for prompt-interactive startup

* perf(cli): defer interactive telemetry startup

* test(cli): add missing assertions and Zed/ACP path coverage for startup prefetch

Address three test coverage gaps identified during code review:

- Assert mockStartEarlyStartupPrefetches in both kitty protocol tests
  (C1: API preconnect call was wired but never verified)
- Add Zed/ACP integration test verifying deferIdeConnection is false
  when getExperimentalZedIntegration returns true (C2: Zed path was
  entirely untested)
- Assert mockStartBackgroundHousekeeping in startup-prefetch test
  (C3: unconditional housekeeping dispatch was never verified)

* docs: move startup prefetch design doc to performance subdirectory

* docs: translate startup prefetch design doc to English

* fix(cli): address startup prefetch review comments

Tighten the startup prefetch follow-up fixes from review while keeping
prompt-interactive telemetry on the fast interactive startup path.

- Preserve Error objects when deferred startup tasks fail
- Remove the unbalanced api_preconnect profiler lifecycle event
- Guard background housekeeping so it only runs for interactive configs
- Document and test prompt-interactive telemetry deferral semantics

* fix(cli): initialize telemetry for prompt-interactive prompts

Ensure sessions launched with an initial interactive prompt have
telemetry ready before the auto-submitted first request runs.

- Exclude prompt-interactive startup from telemetry deferral
- Pass a post-render telemetry option through interactive UI startup
- Skip duplicate post-render telemetry startup for initial prompts
- Update tests to cover the first-prompt telemetry guarantee

Note: Plain interactive TUI startup still defers telemetry post-render.

* fix(cli): preserve startup first-request guarantees

Keep deferred startup work from weakening first-request behavior in
interactive sessions that submit prompts automatically or remotely.

- Store telemetry deferral on Config and reuse that decision at render time
- Keep IDE startup awaited for prompt-interactive and input-file sessions
- Add a timeout for deferred IDE connection failures
- Cover ordinary interactive telemetry deferral and IDE startup edge cases

* fix(cli): make post-render IDE connection opt-in

Default startInteractiveUI to the already-connected IDE path so future
callers do not accidentally connect twice when initializeApp used its
eager default.

- Change the post-render IDE connection default to false
- Update startInteractiveUI tests to assert the safer default

* perf(cli): surface deferred IDE connection status

Make ordinary interactive IDE startup visible while preserving the
post-render prefetch path and first-paint performance tradeoff.

- Emit deferred IDE connection lifecycle events for connecting, success,
  and failure states
- Surface IDE startup status in the TUI footer without blocking input
- Log late underlying IDE failures after timeout for better diagnostics
- Document telemetry deferral tradeoffs and add startup lifecycle tests

---------

Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
2026-07-07 15:31:55 +00:00
qqqys
e3d7d10d1d
[codex] add natural channel memory intents (#6376)
* feat(channels): add natural channel memory intents

* fix(channels): add explicit guard and exhaustiveness check for clear_confirm intent

The clear_confirm path was handled as implicit fall-through at the bottom
of handleChannelMemoryIntent. If a new intent kind were added to the
ChannelMemoryIntent union, it would silently execute clearChannelMemory
without user confirmation — a data-loss risk.

Add explicit if (intent.kind === 'clear_confirm') guard and a
const _exhaustive: never assertion so TypeScript flags any unhandled
kinds at compile time.

* fix(channels): close session leak in classifier and fix regex separator

- BridgeChannelMemoryIntentClassifier now wraps prompt() in try/finally
  to always call cancelSession(), preventing daemon session leaks on
  every classifier invocation. Cleanup errors are caught so they cannot
  mask a successful classification result.
- Add missing optional punctuation separator to the 以后记住 regex
  pattern for consistency with other Chinese remember patterns.

* fix(channels): enforce pending clear state for channel memory confirmation

The clear_confirm intent executed clearChannelMemory directly without
verifying a prior clear_request was issued for the same chat. Any
authorized user could clear any chat's memory by sending the confirmation
phrase standalone, bypassing the two-step flow.

Add a per-target pending clear map (chatId + threadId, 60s TTL) that
is set during clear_request and verified+consumed during clear_confirm.
Standalone confirmation phrases now get rejected with a prompt to
issue the clear request first.

* fix(channels): include senderId in pendingClears key to prevent cross-user confirmation

User A could initiate clear_request in a group chat and User B could
confirm it, since the pending key only included chatId+threadId.
Add senderId to the key so only the user who initiated the clear
can confirm it.

* fix(channels): harden memory intent review fixes

* fix(channels): cover memory clear sender guard

* fix(channels): block group memory mutations

* fix(channels): avoid ambiguous memory saves

* test(channels): cover memory classifier cleanup

* test(channels): cover memory clear expiry

* fix(channels): restore channel memory slash aliases

* test(channels): cover memory intent edge cases
2026-07-07 15:25:01 +00:00
qqqys
467b292b50
feat(channels): add WeCom intelligent robot channel (#6436)
* feat(channels): add WeCom smart bot channel

* fix(channels): harden wecom review suggestions

* fix(channels): address wecom critical review

* fix(channels): include wecom mixed voice text

* fix(channels): tighten wecom outbound media

* fix(channels): harden wecom outbound sends

* fix(channels): address wecom review blockers

* fix(channels): address wecom review followups

* fix(channels): harden wecom inbound handling

* fix(channels): address wecom auth and media review

* fix(channels): tighten wecom inbound cleanup

* fix(channels): harden wecom media safety

* fix(channels): address wecom review typecheck

* fix(channels): harden wecom media review gaps

* fix(channels): address wecom review blockers

* fix(channels): tighten wecom media edge cases

* fix(channels): address wecom review blockers

* fix(channels): address wecom media review blockers

* fix(channels): address wecom review follow-ups

* fix(channels): address wecom review blockers

* fix(channels): close wecom review blockers

* fix(channels): close wecom preflight dedup race

* fix(channels): close wecom review gaps

* fix(channels): harden wecom kick reconnect

* fix(channels): defer wecom session resolution

* fix(channels): clean wecom session attachments

* fix(channels): harden wecom reconnect and media cleanup

* fix(channels): address wecom review diagnostics

* fix(channels): improve wecom diagnostics

* fix(channels): reset wecom kick retries

* fix(channels): improve wecom diagnostics

* fix(channels): preserve sync cancel preflight

* fix(channels): close wecom connection and ssrf gaps

* fix(channels): clean coalesced wecom attachments

* fix(channels): bound wecom sdk connect wait

* fix(channels): scope wecom untracked attachment cleanup

* fix(channels): block wecom nat64 local-use ssrf

* fix(channels): harden wecom media handling

* fix(channels): harden wecom group gates

* fix(channels): bound wecom kick reconnect cycles

* fix(channels): drain loop collect prompts directly

* fix(channels): align wecom buffer hooks

* fix(channels): harden wecom delivery failures

* fix(channels): recover from wecom attachment write failures

* fix(channels): surface wecom media send failures

* fix(channels): harden wecom replay and reconnect

* fix(channels): clarify wecom partial delivery cleanup

* fix(channels): close wecom rejected downloads

* fix(channels): retain wecom dedup after processing starts

* fix(channels): harden wecom reconnect and media errors

* fix(channels): add wecom media error context

* fix(channels): improve wecom dns diagnostics

* fix(channels): keep wecom kick retry alive

* fix(channels): allow wecom quoted bot replies

* fix(channels): preserve wecom code fences across chunks

* fix(channels): harden wecom reconnect lifecycle

* fix(channels): report wecom media dir setup failures

* fix(channels): harden wecom reconnect recovery

* fix(channels): align wecom review fixes

* fix(channels): harden wecom marker parsing

* fix(channels): keep wecom reconnect timers alive

* fix(channels): handle wecom tilde fences

* fix(channels): preserve wecom fence state

* fix(channels): clean up wecom attachment races

* fix(channels): bind wecom media reads to file handles

* fix(channels): prevent wecom symlink media opens

* fix(channels): address wecom review blockers

* fix(wecom): remove media URL from error messages to prevent credential leakage

The guardedHttpsDownload error messages included rawUrl (truncated to 120
chars), which leaks private WeCom media download URLs into stderr and log
aggregation systems. Remove the URL from redirect and HTTP error messages.

* fix(wecom): address review feedback — tests, security, correctness

- Remove stale URL assertions from media download error tests (the error
  messages no longer include raw URLs after the credential-leak fix)
- Redact sensitive fields (secret, aeskey, token, password, authorization)
  in formatSdkError's JSON.stringify fallback to prevent credential
  leakage in logs
- Add indented code block detection to findCodeRanges so [IMAGE: path]
  inside 4-space/tab-indented code is not stripped as a media marker
- Add disconnectGeneration guard before mkdirSync in downloadAttachments
  to prevent orphaned temp directories when disconnect() races with
  in-flight attachment downloads

* fix(wecom): wrap client.disconnect() in catch block to preserve connection error

In the connect() catch block, client.disconnect() could throw (e.g. if
the WebSocket was already destroyed), masking the original connection
error. Wrap in try/catch so cleanup failures never shadow the root cause.

* fix(channels): address wecom reconnect review blockers

* fix(channels): harden wecom reconnect review fixes

* fix(channels): harden wecom review blockers

* fix(channels): address wecom review blockers

* fix(channels): preserve unsupported wecom media markers

* fix(channels): address wecom reliability suggestions

* fix(channels): allow wecom retry after early drops

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-07 15:24:19 +00:00
jifeng
971d4ba27e
feat(web-shell): add column reorder, resize, and freeze controls to markdown table (#6444)
* feat(web-shell): add markdown table column controls

Support resizing, reordering, and freezing table columns while preserving visible-order copy behavior and selection stability.

* fix(web-shell): address markdown table review feedback

* fix(web-shell): refine markdown table column reordering

* fix(web-shell): address markdown table review feedback

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-07 14:18:07 +00:00