Commit graph

494 commits

Author SHA1 Message Date
OrbitZore
ec9c36ef82
feat(channels): add GitLab polling channel adapter (#7862)
* feat(channels): add GitLab polling channel adapter

Poll GitLab todos via @gitbeaker/rest, dispatch notes through the
existing PollingChannelBase pipeline. Key design points:

- action_prompt_template config drives event filtering and metadata
  rendering (unconfigured actions are skipped)
- Per-repo cursor (repo[chatId].last_read) as notes window lower bound,
  global lastProcessedAt for todo-level dedup
- mark_done after successful processing; failure skips mark_done for
  retry on next poll
- Mention gating delegated to base GroupGate (adapter only sets
  isMentioned flag)
- First-contact body fallback for todos with no notes (e.g. mention in
  issue description)

* fix(channels/gitlab): persist cursor after each successful todo

Call saveCursor() immediately after advancing lastProcessedAt so that
progress is durable even if the process crashes mid-poll. Also removes
the local watermark variable in favor of direct assignment.

* fix(channels/gitlab): persist cursor on every advancement including skips

* fix(channels/gitlab): address review critical issues

- Remove non-functional proxyAgent (gitbeaker doesn't support it)
- Construct repo_url from host + path (API doesn't return web_url)
- Handle directly_addressed action (falls back to mentioned template)
- First-contact fetches target description instead of using todo.body
- Move todo.project dereference inside try block
- Filter confidential notes
- Update channel-registry.test.ts for gitlab entry

* fix(channels/gitlab): address review suggestions

- Warn on connect if action_prompt_template is not configured
- Guard todo.target.iid before use
- Skip paths now mark_done (best-effort) to clean GitLab UI
- Remove postErrorComment (avoids duplicate comments on retry)
- Fetch only first page of notes (desc, maxPages:1, perPage:100)
  instead of paginating entire note history
- Extract fetchRecentNotes for single-page windowed enumeration

* refactor(channels/gitlab): simplify to todo.body dispatch, add description mention support

- Remove notes API fetching; dispatch todo.body directly
- Detect description mentions via target_url anchor (#note_ absence)
- Always fetch target description for %description% metadata
- Remove per-repo cursor; dedup via cursor + mark_done only
- Cursor advances regardless of success/failure (no retry)
- Use zod for cursor validation
- Rename template vars to GitLab terminology:
  %project% %project_url% %target_type% %iid% %title% %description% %todo_id%
- Support %% escape for literal percent

* docs(channels): add GitLab adapter documentation

- New user guide: docs/users/features/channels/gitlab.md
- Update _meta.ts navigation
- Update developer adapter matrix and SDK list

* fix(channels/gitlab): use correct Issues.show(issueIid, { projectId }) signature

* chore: regenerate NOTICES.txt for new gitlab channel dependencies

* fix(channels/gitlab): address review suggestions

- Add todo.project null guard (item 2)
- Single-pass regex for %% escape + %var% substitution (item 4)
- sendThreadMessage throws directly on undefined threadId (item 5)
- Dedup fetchDescription with per-poll cache (item 6)
- Remove per-todo saveCursor; base class saves after pollOnce (item 7)
- Add undefined threadId test (item 8)
- Expand confidential notes limitation in docs (item 3)

* test(channels/gitlab): add mention tests, directly_addressed coverage, skip assertions, temp cleanup

- New mention.test.ts: 14 cases for testBotMention/stripBotMention/escapeRegex
- Add directly_addressed fallback test
- Skip tests now assert TodoLists.done + cursor advancement
- afterEach cleans up mkdtempSync temp dirs

* fix(channels/gitlab): address review round 4

- Non-mention actions (assigned, etc.) set forceMentioned=true to bypass GroupGate
- Merge dead note-filter tests into single 'skips todo authored by bot'
- Log fetchDescription errors to stderr instead of silent swallow
- Post error comment on issue/MR when handleInbound fails (best-effort)

* fix(channels/gitlab): always force isMentioned=true, remove regex re-derivation

The action_prompt_template config is already the event filter, and
GitLab has already decided the mention when creating the todo.
Re-deriving isMentioned via regex on todo.body causes permanent
message loss when the regex misses (description mention + fetch
failure, group mentions). Always set forceMentioned=true so
GroupGate never drops a todo that passed the template filter.

* fix(channels/gitlab): propagate fetchDescription errors for description mentions

For note mentions, description is metadata-only — fetch failure is
logged and swallowed. For description mentions, description IS the
message — fetch failure now propagates to the outer catch, which
posts the ⚠️ error comment so the user knows to re-mention.

* perf(channels/gitlab): clean up stale todos, skip unnecessary fetchDescription

- Mark stale todos (updated_at <= cursor) as done on each poll to
  prevent perpetual re-fetching of pre-existing pending todos
- Skip fetchDescription for note mentions when template does not
  contain %description%, saving one API call per todo
- Update docs: stale todo cleanup, error comment on failure

* docs(channels/gitlab): clarify requireMention is bypassed, template is the real filter

* Apply suggestions from code review

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

* fix(channels/gitlab): use todo ID cursor instead of timestamp to eliminate equal-timestamp loss

Timestamp-based cursors (second granularity) could silently destroy
todos sharing the same updated_at as the cursor boundary. Switch to
monotonically increasing todo IDs which are unique and collision-free.

Add initialized flag to preserve first-start drain semantics: pre-existing
pending todos are marked done without dispatch on the first poll cycle.

* fix(channels/gitlab): harden first-poll drain, add ordering tests, fix lockfile

- Replace Math.max(...spread) with reduce to avoid RangeError on large
  backlogs (~100k+ todos). Move initialized=true after the drain work so
  any throw retries the drain instead of falling through to dispatch.
- Add unit tests: identical-timestamp delivery and id-order-when-updated_at-disagrees
  (kills M2 sort mutant).
- Align lockfile: file:../base → ^0.21.0 for channel-base dep.

* fix(channels/gitlab): include dot in mention lookahead for GitLab usernames

GitLab usernames may contain dots (e.g. bot.name). The lookahead
character class inherited from GitHub omitted '.', causing @bot.name
to match as @bot. Add '.' to the negated class.

* docs(channels/gitlab): align docs with ID cursor and drain semantics

- Add first-poll drain as step 2 in How It Works
- Clarify GroupGate always passes (isMentioned forced true)
- Document initialized flag in Known Limitations

* Apply suggestions from code review

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

* fix(channels/gitlab): align package version and channel-base dependency to 0.21.1

Bump version from 0.21.0 to 0.21.1 to match other channel packages after
upstream merge. Pin @qwen-code/channel-base to exact 0.21.1 instead of
^0.21.0, matching the convention used by other published channels.

* fix(channels/gitlab): regenerate lockfile to match package.json versions

Manually add only gitlab-related lockfile entries (workspace, @gitbeaker
packages, transitive deps, channel-gitlab link) without unrelated npm
normalization churn.

* test(channels/gitlab): add regression tests for first-poll drain hardening

Two tests that kill the M1 (Math.max spread RangeError) and M2 (flag
ordering) mutants which survived the original 46-test suite:

- 150k todo drain verifies reduce() handles large backlogs without
  RangeError and without dispatching
- Drain throw verifies initialized stays false so the next poll retries
  the drain instead of falling through to dispatch

Test file duration: ~40ms → ~170ms.

* docs(channels/gitlab): clarify groupPolicy must be "open" and add runtime warning

The default groupPolicy "disabled" silently drops all mentions — todos are
marked done and cursor advances, but no dispatch occurs. Fix misleading docs
that said "GroupGate always passes" (only true at groupPolicy: "open") and
add a connect()-time warning when groupPolicy is not "open".

* fix(channels/gitlab): correct xcase integrity hash in lockfile

The manually added xcase entry had a typo in the sha512 hash (ys → ks),
causing npm ci EINTEGRITY failures in CI.

* fix(channels/gitlab): correct requester-utils integrity hash in lockfile

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

* fix(channels/gitlab): allow groupPolicy "allowlist" in warning and docs

The groupPolicy warning and docs incorrectly stated that groupPolicy
must be "open". In reality "allowlist" with the project listed also
works because isMentioned is forced true and GroupGate only requires
the group to be listed. Also fix the inaccurate "no error is logged"
claim — ChannelBase logs preflight rejected reason=group_disabled.

Fixes R5-🟡3 from PR #7862 review.

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-29 14:28:31 +00:00
zhangxy-zju
1d55d290a0
feat(web-shell): render streaming charts with markdown-chart (#7916)
* feat(web-shell): use markdown-chart for streaming charts

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

* fix(web-shell): preserve legacy chart ref caching

* fix(web-shell): preserve chart safety and loader stability

* test(web-shell): strengthen markdown chart safety contracts

* test(web-shell): cover legacy chart streaming adapter
2026-07-29 06:04:56 +00:00
qwen-code-ci-bot
3144046c6b
chore(release): v0.21.1 (#7958)
* chore(release): v0.21.1

* docs(changelog): sync for v0.21.1

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-29 00:25:30 +00:00
qqqys
bd2c0b47f1
feat(core): add full-resolution image zoom tool (#7809)
* feat(core): add full-resolution image zoom tool

* chore(vscode): refresh third-party notices

* fix(ui): satisfy zoom image display drift checks

* fix(web-shell): translate zoom image tool

* fix(core): wire sharp into packaging and load it lazily (#7809)

Externalize sharp in esbuild and declare it (plus the @img platform
binaries) in the published package's optionalDependencies so an
npm-installed CLI resolves the native binding. Import sharp dynamically
inside execute() so a missing binding returns a bounded tool error
instead of crashing startup during strict tool warmup, and so the module
is only loaded when zoom_image actually runs. Also pin the EXIF
auto-orientation test with a discriminating fixture and cover the
.qwenignore and y1>=y2 validation paths.

* fix(core): gate zoom_image at execute time and cap tiny-crop upscale (#7809)

Register zoom_image unconditionally and move the image-modality check to
execute time so first-run sessions and hot /model switches resolve the
tool without re-running initialize(). Cap magnification at 8x so a tiny
crop no longer inflates to the full image-token budget. Drop the redundant
@img/* platform pins; sharp's own optionalDependencies install the matching
binary for each OS/arch.

* fix(core): emit zoom_image file telemetry and cover guard branches (#7809)

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

---------

Co-authored-by: qwen-code-autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-27 23:40:26 +00:00
易良
083f5b97d0
fix(release): publish all channel packages, not just channel-base (#7845)
* fix(release): publish all channel packages, not just channel-base

The release workflow only published @qwen-code/channel-base while the
other 7 channel packages (dingtalk, feishu, github, qqbot, telegram,
wecom, weixin) were never published to npm despite being non-private
and version-synced.

Add a loop step after channel-base that publishes all remaining
channel packages with the same npm tag and dry-run support.

* chore(release): record channel preview packages

* chore(release): align channel preview versions

* chore(release): restore channel source versions

* chore(channels): emit declaration maps
2026-07-27 17:55:08 +00:00
jinye
9bdc62c74b
perf(cli): replace comment-json settings parser (#7747)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-26 14:42:51 +00:00
OrbitZore
62e009a952
feat(channels): GitHub polling adapter with notification-as-wakeup architecture (#7632)
* feat(channels): add GitHub polling adapter with notification-as-wakeup architecture

Introduce a GitHub channel adapter that monitors notifications and
responds to @mentions on issues/PRs by posting comments. Uses
last_read_at as a per-thread watermark for comment enumeration,
replacing the unreliable latest_comment_url approach.

Foundation changes to ChannelBase:
- sendThreadMessage for thread-targeted delivery (IM adapters unchanged)
- Envelope.metadata appended to prompt after command parsing
- chat_thread session scope (channel:chatId:threadId) prevents
  cross-repo session collision
- polling-helpers: testBotMention/stripBotMention (separate detection
  from stripping, no whitespace collapsing), cursor persistence,
  abortableSleep

GitHub adapter design:
- Notifications as wake-up signals only (unread filtering)
- listComments enumeration with last_read_at watermark
- Bot self-comment filtering, case-insensitive mention regex
- In-memory recentlyProcessed set for mark-read failure dedup
- First-contact: new issue body @bot triggers processing
- Error comment + cursor advance on handleInbound failure
- pollInterval minimum 60s, exponential backoff 2s-30s

* refactor(channels): extract PollingChannelBase from polling-helpers

Replace the loose polling-helpers module with a PollingChannelBase<Cursor>
abstract class that encapsulates the poll loop, cursor persistence (JSON,
atomic write), exponential backoff, and start/stop lifecycle. Subclasses
implement only pollOnce() and createInitialCursor().

- Delete polling-helpers.ts (cursor fns + abortableSleep moved into base)
- Move mention utilities (testBotMention/stripBotMention) to github pkg
- GithubAdapter now extends PollingChannelBase<{ lastProcessedAt }>

* fix(channels): remove Gitea/GitLab mention from sendThreadMessage JSDoc

* fix(channels): match /pulls/N in notification subject URL

GitHub PR notifications use /repos/{owner}/{repo}/pulls/{N} in
subject.url, not /issues/{N}. The regex only matched /issues/,
causing PR notifications to be skipped and marked read.

Also sets threadId to 'pr:N' for PRs (was always 'issue:N').

* test(channels): add PR body first-contact unit test

Verify that PR notifications with @mention in the body (not a comment)
correctly trigger the first-contact path: extractFromSubjectUrl matches
/pulls/N, listComments returns empty, tryFirstContactBody fetches the
PR body and dispatches to handleInbound with threadId 'pr:N'.

* feat(channels): read pollInterval from channel config in PollingChannelBase

Move pollInterval config reading from GithubAdapter to the base class.
The user's configured pollInterval in settings.json is now respected
directly without a minimum enforcement. Defaults to 60000ms when not
configured.

* fix(channels): prepend metadata before prompt text

Agent sees issue/PR context (type, title, URL) before the user's
request, improving comprehension. Metadata is still appended after
slash-command parsing so commands are not affected.

* refactor(channels): route all ChannelBase delivery through sendThreadMessage

Replace all internal sendMessage calls with sendThreadMessage, passing
envelope.threadId (or target.threadId / undefined) so polling adapters
can deliver to the correct thread. IM adapters are unaffected — the
default sendThreadMessage falls through to sendMessage.

* docs(channels): document sendThreadMessage delivery architecture

* fix(channels): address review findings

- Cap recentlyProcessed Set at 10k entries to prevent unbounded growth
- Validate cursor JSON shape (non-null object) in loadCursorFromDisk
- sendThreadMessage falls through to sendMessage when threadId is
  undefined instead of silently dropping
- Remove duplicate pollInterval from GithubConfig (now in ChannelConfig)
- Fix chat_thread routing key trailing colon when threadId is undefined

* docs(channels): fix metadata JSDoc — prepended, not appended

* fix(channels): use recentlyProcessed dedup for first-contact body

Replace the fragile createdAt-vs-cursor check in tryFirstContactBody
with the recentlyProcessed set. The cursor advances globally based on
notification updated_at — when a different notification with a later
updated_at is processed first, the cursor can advance past the issue's
created_at, causing the first-contact check to incorrectly skip the
issue body (forget reply bug, found in E2E TC-2b).

* refactor(channels): two-layer dedup for GitHub adapter

Layer 1: global cursor filters notifications by updated_at (sorted
ascending, old first). Layer 2: server-side last_read_at filters
comments by created_at (sorted ascending).

- Delete recentlyProcessed Set (no longer needed)
- Sort notifications by updated_at ascending before processing
- Sort comments by created_at ascending before processing
- Pass latest comment created_at to markThreadAsRead as last_read_at

* fix(channels): address review findings on GitHub adapter

Blockers:
- sessionScope: add defaultSessionScope to ChannelPlugin, apply in
  parseChannelConfig so router and adapter agree on 'chat_thread'
- channel-registry.test.ts: add 'github' to expected type list

Should-fix:
- Replace per-thread markThreadAsRead (PATCH) with bulk
  markNotificationsAsRead (PUT /notifications + last_read_at).
  API errors stop the batch without marking failed notifications
  read; handleInbound errors still advance (error comment posted).
- connect() throws on bot identity failure instead of failing open
- metadata appended after promptText (inside sender attribution)
- isSharedSessionTarget includes 'chat_thread' scope

Nits:
- startPollLoop re-entrancy guard
- clean-package-build-artifacts.js includes github
- index.ts re-exports GithubChannel

* fix(channels): use max updated_at of all fetched notifications as last_read_at

Prevents re-fetching the same notifications in the next poll cycle.
The bulk PUT /notifications marks all fetched notifications as read
up to the max updated_at, regardless of per-notification success.

* fix(channels): address review round 2 findings

- #12: loadCursorFromDisk rejects arrays
- #13: pollInterval validates positive finite number
- #19: first-contact gate uses dispatchedMention flag (not newComments.length)
- #25: stripBotMention no longer trims (preserves indentation)
- #27: remove adapter-level requireMention, unify on GroupGate
- #31: add chat_thread SessionRouter routing key tests
- #33: clear metadata on collect-mode synthetic envelope
- #35: fix PollingChannelBase.test import path
- #36: add @octokit/rest to 15-channel-adapters.md dependencies

* docs(channels): document known limitations for GitHub adapter

- First start skips existing unread notifications (cursor = now)
- Requires classic PAT (fine-grained PATs lack notifications API)
- PR review comments not enumerated (issue comments only)

* fix(channels): address review round 3 findings

- #9: buildMetadata derives web URL from baseUrl (GHE support)
- #12: sendThreadMessage throws on invalid threadId format
- #19: mention lookbehind matches cc:@bot and "@bot" patterns
- #23: cursor file name uses sha256 hash to prevent collision
- #26: test verifies cursor persistence to disk
- #31: postErrorComment double-failure logs to stderr
- #45: tests use mkdtempSync isolation instead of real QWEN_HOME

* fix(channels): pass threadId through pairing flow + sendResponseMessage test

- #13+16: onPairingRequired receives envelope.threadId and passes it
  to sendThreadMessage, so pairing codes are delivered on threaded
  channels (GitHub) instead of throwing
- #6: add test verifying sendResponseMessage resolves threadId from
  router.getTarget and passes it to sendThreadMessage

* fix(channels): pass proxy to Octokit for daemon-worker environments

- #44: read this.proxy from ChannelBaseOptions and pass
  HttpsProxyAgent to Octokit request.agent, matching the
  Telegram adapter pattern

* fix(channels): address review findings — immutable senderId, comment time window, validateCursor, retry wrapper

- senderId uses immutable user.id; allowedUsers resolved to IDs at connect
- Comment filter upper bound: updated_at <= maxUpdatedAt (batch window)
- Per-notification errors use continue (best-effort), not break
- validateCursor() virtual hook for subclass cursor shape validation
- sendThreadMessage/postErrorComment wrapped in githubApi() retry
- webOrigin handles default api.github.com → github.com
- Docs: classic PAT only, markNotificationsAsRead, dedup claims removed
- Tests: threadId priority, metadata consumption, defaultSessionScope,
  QWEN_HOME isolation, persistent mock rejection

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

* fix(channels): mark notifications read before processing to prevent duplicate replies

Bot's own replies bump notification updated_at past the pre-captured
maxUpdatedAt, so markNotificationsAsRead(maxUpdatedAt) failed to mark
them read — the next poll re-fetched the same comments and replied
again.

Move markNotificationsAsRead + cursor advance before the processing
loop (best-effort delivery). This is safe because bot's own comments
do not flip notifications back to unread. Update docs to reflect the
new poll cycle order and best-effort semantics.

* fix(channels): update sender gate after allowedUser ID resolution and harden tests

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

* fix(channels): cursor-based comment window to prevent duplicate replies

PUT /notifications is async (202) with a last_read_at cutoff — the
bot's reply bumps updated_at past the cutoff before the server
processes the mark, so the notification is never marked read and gets
re-fetched on the next poll, causing duplicate replies.

Use the cursor value before advancement as an exclusive lower bound
for the comment enumeration window: (windowSince, maxUpdatedAt].
Comments already eligible in a previous poll are excluded regardless
of whether the mark succeeded. Zero new persistent state.

* fix(channels): cursor-based comment window to prevent duplicate replies

PUT /notifications is async (202) with a last_read_at cutoff — the
bot's reply bumps updated_at past the cutoff before the server
processes the mark, so the notification is never marked read and gets
re-fetched on the next poll, causing duplicate replies.

Use the cursor value before advancement as an exclusive lower bound
for the comment enumeration window, with per-notification last_read_at
as the preferred lower bound when available (server-side per-thread
watermark). Comments already eligible in a previous poll are excluded
regardless of whether the mark succeeded. Zero new persistent state.

* fix(channels): address review findings — null guard, cursor validation, metadata dedup, abortable sleep, docs

- Guard against null notification.subject.url in pollOnce
- Validate lastProcessedAt is a parseable date in validateCursor
- Add metadata: undefined to second collect-mode drain path
- Refactor abortableSleep as protected method on PollingChannelBase
- Fix docs: requireMention is nested under groups.*
- Add tests: chat_thread shared session, dispatchedBodies eviction,
  cursor enumeration window, last_read_at in mention tests

* docs(channels): sync docs with implementation — cursor shape, error handling, GitHub adapter tables, first-contact

- Design doc: update Cursor to { lastProcessedAt, dispatchedBodies? }, add
  validateCursor date check, abortableSleep protected method, break-on-error
  semantics, subject.url null guard
- Developer docs: add GitHub to adapter table and adapter matrix
- User guide: add first-contact step to How It Works, clarify mark-before-process

* fix(channels): address review round 2 — error dedup, abortable retry, backoff reset, window test

- Record dispatchedBody on first-contact handleInbound failure to prevent
  duplicate error comments when mark-read async hasn't taken effect
- Use abortableSleep instead of raw setTimeout in githubApi retry so
  disconnect() can interrupt rate-limit cooldowns
- Reset consecutiveErrors in startPollLoop so stop/restart cycles don't
  inherit stale elevated backoff
- Add test for cursor window client-side lower-bound exclusion filter

* fix(channels): address review round 3 — cursor validation, error dedup, sender gate, bot-self body

- validateCursor: normalize falsy non-array dispatchedBodies (false/0/""/null)
  to [] instead of passing them through to .includes() which throws TypeError
- Set dispatchedMention after postErrorComment to prevent first-contact from
  posting a duplicate error comment on the same thread
- Only set dispatchedMention when the sender passes the sender gate, so a
  disallowed commenter's mention no longer suppresses a valid first-contact
  body from an allowed issue author
- Skip bot-authored issue bodies in tryFirstContactBody to prevent
  self-response loops under open sender policy

* fix(channels): address review suggestions — test coverage, cursor filename, assertion precision

- Pairing flow: add threadId pass-through regression test
- pollInterval: add table-driven edge cases (0, -1, NaN, Infinity, string)
- Add null-URL notification followed by valid notification batch test
- Fix comment window test to assert paginate call 3 (listComments) not call 2
- Truncate cursor filename encoded prefix to 200 chars (filesystem 255 limit)
- Assert mark-read uses batch maxUpdatedAt, not just { read: true }
- Assert real GitHub plugin declares defaultSessionScope chat_thread
- Add invocationCallOrder assertion for mark-before-process ordering

* fix(channels): address review round 4 — allowedUsers throw on resolve failure, crash table fix, mark-read failure test

* fix(channels): address review round 5 — created_at filter, retry-after NaN guard, retry/sendThreadMessage tests, docs fixes

* fix(channels): address ci-bot review 4778587403 — reconnect idempotency, github type enumerations, retry/webOrigin tests

* chore(channels): align channel-github version to 0.21.0 after upstream merge

* chore(channels): update package-lock.json for channel-github 0.21.0

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

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: OrbitZore <orbitzore@users.noreply.github.com>
2026-07-25 09:31:50 +00:00
jinye
8f667f5bdc
feat(integrations): add retrieval-only external context search (#7586)
* feat(integrations): add direct external context provider

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

* fix(integrations): harden external context failure handling

Preserve provider timeout classification, reject ambiguous Mem0 statuses, release rejected response bodies, and clarify credential and workspace deployment boundaries.

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

* refactor(integrations): narrow external context to retrieval

Limit Phase 1 to one provider-bound search tool, remove hooks and writes, and document the direct profile's actual permission and isolation boundaries.

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

* fix(integrations): harden external context deployment

Pin the managed MCP source through an administrator-owned command-line configuration, document the Direct Profile trust boundary, and remove unused logging/runtime abstractions.

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

* fix(integrations): preserve external context results

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

* fix(integrations): honor provider proxy settings

Install an environment-aware dispatcher before the external context MCP server starts so enterprise egress proxy and NO_PROXY settings apply to provider requests. Document the managed launcher environment and cover startup wiring.

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

* fix(integrations): diagnose invalid proxy settings

Classify proxy dispatcher construction failures as sanitized configuration errors so managed deployments can identify an invalid proxy environment without exposing proxy credentials.

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-25 08:18:27 +00:00
qwen-code-ci-bot
9e7acec863
chore(release): v0.21.0 (#7675)
* chore(release): v0.21.0

* docs(changelog): sync for v0.21.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-24 13:47:34 +00:00
Damian Tometzki
3b3a593600
Fix(cli): use npm view for update check instead of update-notifier (#7515) (#7528)
* fix(cli): use npm view for update check instead of update-notifier (#7515)

* fix(cli): use npm view for update check instead of update-notifier (#7515)

* fix(cli): accept array-wrapped npm view output in update check (#7515)

npm 11+ prints `npm view <pkg> dist-tags.<tag> --json` as ["0.20.1"]
instead of "0.20.1", so the strict string check re-broke the update
check with "Invalid npm latest version response". Accept both shapes.

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

* chore(cli): drop update-notifier dependency and dead install-detection code (#7515)

Version checking now goes through npm view for every install type, so:
- replace the update-notifier UpdateInfo type import with a local interface
  and remove update-notifier / @types/update-notifier from dependencies
- remove isGlobalNpmInstallation and looksLikeNpmPackagePath, which no
  longer have any production callers, along with their tests

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

* docs(cli): fix npm version in array-output comment (npm 12+, not 11+)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-23 08:36:35 +00:00
ytahdn
80863c0858
feat(web-shell): show subagent sessions in detail panel (#7380)
* feat(web-shell): show subagent sessions in detail panel

* fix(web-shell): harden subagent session details

* chore(sdk): account for subagent client APIs

* fix(core): keep subagent resume history valid

* fix(subagents): stabilize detail session streaming

* test(cli): update telemetry route count

* fix(subagents): address review feedback

* fix(subagents): harden transcript refresh state

* fix(subagents): address maintainer review

* fix(subagents): preserve compact status details

* fix(subagents): address review feedback round 3 (#7380)

* fix(subagents): address remaining detail review feedback

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
2026-07-22 02:31:08 +00:00
qwen-code-ci-bot
97a834bc0d
chore(release): v0.20.1 (#7461)
* chore(release): v0.20.1

* docs(changelog): sync for v0.20.1

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-22 02:00:22 +00:00
qwen-code-ci-bot
bc0e2cd180
chore(release): v0.20.0 (#7211)
* chore(release): v0.20.0

* docs(changelog): sync for v0.20.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-19 07:35:40 +00:00
qwen-code-ci-bot
3ede6261ea
chore(release): v0.19.12 (#7176)
* chore(release): v0.19.12

* docs(changelog): sync for v0.19.12

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-18 14:58:42 +00:00
Shaojin Wen
6dce543491
feat(web-shell): add a workspace Goals page, and stop losing /goal on daemon resume (#6561)
* fix(goals): persist goal cards and restore the hook on daemon resume

In daemon mode a `/goal` was silently lost whenever its session was
reloaded or `qwen serve` restarted: the goal card vanished from the
transcript and the Stop hook was never re-registered, so the loop simply
stopped advancing. The TUI does neither of these things wrong; the ACP
path was missing both halves.

Goal cards were only ever emitted as live SSE `_meta` (MessageEmitter's
emitGoalStatus / emitGoalTerminal) and never written to the transcript,
so the one durable store — the ChatRecord JSONL — had nothing to restore
from. Record them from Session.emitGoalStatus, the single choke point for
`set` and `cleared` (the sessionGoalClear ext method routes through it
too), and from the goal terminal observer for `achieved` / `failed` /
`aborted`. Persisting `cleared` matters on its own: without it the last
stored card stays `set`, and a later resume would revive a goal the user
explicitly dropped.

HistoryReplayer dropped those records on the way back out — it reads only
`item['text']`, and a goal card has no `text` field — so re-emit them as
`_meta.goalStatus`. Per-iteration `checking` cards are skipped: a TUI
transcript stores one per stop-hook turn and clients suppress them as
noise. That costs no fidelity, because restore reads the records directly
rather than the replay output.

With the transcript carrying the goal again, add #restoreGoalOnResume to
loadSession and unstable_resumeSession, alongside #restoreWorktreeOnResume.
It rebuilds the goal cards from the resumed ChatRecords (they live inside
system/slash_command records' outputHistoryItems) and reuses the existing
findGoalToRestore / findLastTerminalGoal / registerGoalHook logic, trust
and hook-policy gates included.

* feat(web-shell): add a workspace Goals page

`/goal` had no visual surface in the web shell. You could set and clear
one from the composer, but the only feedback was a status-bar pill and a
transcript card, and there was no way to see every goal running in the
workspace at once. Add a full-pane Goals page alongside Scheduled Tasks.

Each row shows the condition, the session driving it, whether the loop is
mid-turn, the judge's turn count and last verdict, and how long the goal
has been running. A row opens its session — the transcript IS the goal's
history — or clears the goal. A form starts a new goal in a fresh session,
so the loop doesn't take over a conversation already in progress.

Reading the goals needs a round trip. They live in the owning `qwen --acp`
child's in-memory store, and serve runs in a separate process holding only
a bridge, so there is nothing local to read. Add a `sessionGoalGet` ext
method that reports one session's goal state, wrap it in
bridge.getSessionGoal (mirroring clearSessionGoal), and have `GET /goals`
fan out over the workspace's live sessions concurrently — one timeout for
a wedged child rather than one per session. A session whose probe rejects
is dropped rather than failing the whole list. Clearing reuses
`POST /session/:id/goal/clear`, so the page and a `/goal clear` typed in
chat take the same path through the daemon.

Only loaded sessions appear, which is the honest answer rather than a
limitation: a goal advances only while its session is resident.

Three entry points: a sidebar button, the status-bar goal pill (now a
button), and a bare `/goal`, which opens the page instead of asking the
daemon to print its status as text — matching how `/schedule` behaves. It
sends no prompt and touches no session, so it works mid-turn too.
`/goal <condition>` and `/goal clear` are unchanged.

The integration test exercises the whole chain against a real daemon:
`GET /goals` -> bridge -> ext method in a spawned `qwen --acp` child.

* fix(web-shell): stop the Goals poll from overlapping itself

`GET /goals` fans out one ext-method probe per live session, and a wedged
child holds it for the bridge's 10s `initTimeoutMs` — the same order as the
10s poll interval. `withActionTimeout` rejects the wait at 30s but never
aborts the underlying fetch, so a fixed `setInterval` could stack several
fan-outs against an already-struggling daemon. `reloadSeqRef` only keeps a
stale response from overwriting state; it does nothing about the pile-up.

Replace the interval with a single self-chaining loop that owns both the
initial load and the polling, scheduling each fetch only once the previous
one has settled. Folding the mount load into the chain matters: left in its
own effect, the first timer would still fire while it was in flight.

Reported by Copilot on #6561.

* fix(goals): address review — clear-keyword condition, silent failures, theme vars

From the /review suggestions on #6561. Applied the ones that held up under
verification; the rest are answered in the PR thread with evidence.

- The New goal form accepted a clear keyword as a condition. It travels as
  `/goal <condition>`, so "clear" (or stop/off/reset/none/cancel) reached the
  daemon as a clear command: the fresh session dropped its own goal the instant
  it was set, with nothing to show for it. Reject it in the form. The keyword
  list and `/goal` arg parsing move to `utils/goalCondition.ts` so the page and
  App share one definition instead of the page reaching into App.

- Starting a goal failed silently. `onCreateGoal` switches to the chat view
  first, which unmounts the Goals page, so the inline form error that
  `sendPrompt` rejection produced was dropped by the page's own unmount guard.
  Surface it as a toast instead.

- `GoalsDialog.module.css` used `var(--destructive, #dc2626)`, but nothing
  defines `--destructive`; the hardcoded fallback stayed the same red in both
  themes. Use `--error-color` and match ScheduledTasksDialog's focus outline.

- `recordGoalStatusItem` swallowed recording failures with a bare `catch {}`.
  Silently losing that write is precisely the failure this recording exists to
  prevent, so log it.

- `GET /goals` dropped failed probes silently — an empty page and a page whose
  probes all failed look identical to the client. Log the dropped sessions and
  their reasons.

Tests: clear-keyword and MAX_GOAL_LENGTH form validation, goalCondition unit
tests, `sessionGoalGet` argument validation, session load surviving a throwing
goal restore, `/goals` drop logging, and a regression test showing `/goal clear`
sent as a prompt does persist its cleared card (a reviewer flagged this as
missing; it is not).

* fix(goals): cap restored conditions, keep goal-creation errors on screen

Second round of review on #6561.

- `restoreGoalFromHistory` re-registered whatever condition the transcript
  held, skipping the 4000-char cap `/goal` enforces at set time. A transcript
  is a file: a corrupted or hand-edited `condition` would ride along in every
  judge call and continuation prompt for the rest of the session. Gate it
  alongside the existing trust and hook-policy gates. `MAX_GOAL_LENGTH` moves
  to `restoreGoal.ts` and `goalCommand.ts` imports it — the reverse direction
  would be a cycle, since goalCommand already depends on this module.

- Starting a goal switched to the chat view before awaiting `sendPrompt`,
  which unmounted the Goals page. The previous commit routed the rejection to
  a toast, but the better fix is not to leave: switch views only once the
  prompt is admitted, so the error lands in the form the user is looking at.
  `GoalsDialog` keeps a toast fallback for the case where the page is closed
  while the prompt is still in flight.

- Move the `debugLogger` declaration below the imports in `restoreGoal.ts`.
  Imports are hoisted so this compiled, but a statement wedged between two
  import blocks is not something to leave behind.

* fix(goals): surface restore/record failures, report unprobed sessions

Third round of review on #6561.

- `debugLogger.warn` no-ops unless a debug session is active
  (`debugLogger.ts:216`), so a failed goal restore and a failed goal-card
  write were both invisible in production — the two failure modes this PR
  exists to fix. Promote them to `writeStderrLine`, which both `ui/App.tsx`
  and `session/Session.ts` already use.

- `GET /goals` now returns `droppedCount`. A brownout in which every probe
  fails returned `{ goals: [] }`, indistinguishable from a workspace with no
  goals — so the user re-creates goals that are already running. The Goals
  page shows a notice when the list is incomplete.

- `running` on the wire is really "the owning session is mid-turn", which a
  manual prompt in that session also sets. Renamed to `hasActivePrompt` so
  the field reports what the daemon actually knows. The UI still maps it to
  Working/Waiting.

- Fix the stale "keep in sync" pointer in `goalCommand.ts`: the clear keywords
  moved from `App.tsx` to `utils/goalCondition.ts` in the previous commit.

Tests for the four coverage gaps the review named: the `systemMessage` fallback
in `goalTerminalEventToHistoryItem` (including the known lossy collapse when
both fields are set), `#restoreGoalOnResume` on an empty transcript,
`listGoals`/`clearGoal` in `actions.ts`, and the `sendPrompt`-after-
`createNewSession` failure path (added last commit). Plus `droppedCount`
projection and the degradation notice.

* test(goals): update the /goals integration test for droppedCount

Adding `droppedCount` to the `GET /goals` payload broke the end-to-end
assertions, which still expected `{ v: 1, goals: [] }`. Caught in review, not
by CI: the Integration Tests job is gated off for this PR, so nothing ran
these against a real daemon after the shape changed.

`droppedCount: 0` is the load-bearing half of the live-session assertion. A
dropped probe also yields an empty `goals`, so the old assertion could not
tell a successful ext-method round trip from a silently failed one.

Re-ran against a spawned `qwen serve` + `qwen --acp` child: green with the
fix, red without it.

* fix(goals): refuse to replay an oversized goal card

`restoreGoalFromHistory` gates the condition at MAX_GOAL_LENGTH, but
`HistoryReplayer` did not: a corrupted or hand-edited transcript could still
ship an unbounded `condition` to every client inside `_meta.goalStatus`. Apply
the same gate at the replay emit site, so neither the card nor the hook
survives an oversized condition.

The gate deliberately does NOT move into `parseGoalStatusItem`, which would be
the tidier-looking place. `findGoalToRestore` and `findLastTerminalGoal` scan
backwards and stop at the FIRST goal card they meet, so dropping a card at
parse time silently promotes the card before it. A transcript ending in an
oversized `cleared` would then restore the `set` that preceded it — resurrecting
a goal the user explicitly cleared, the exact failure persisting `cleared` was
added to prevent. Parsing therefore stays lossless and the length check lives at
each consumer.

Tests pin both halves: replay refuses at 4001 and emits at exactly 4000, and
three scanner tests show an oversized card still wins the scan so restore can
fail closed on it.

* fix(goals): keep the terminal observer alive across ACP resume

Addresses the latest review round on #6561.

`registerGoalHook` calls `unregisterGoalHook`, which clears the session's
goal-terminal observer. The ACP restore path passes no `addItem`, so nothing
reinstalled it: a restored goal reached achieved/failed/aborted with no wire
update and no persisted terminal card, and the next reload revived a goal that
had already finished. The no-goal branch unregisters too, so every ACP resume
lost the observer, not just ones with a goal. `#restoreGoalOnResume` now
reinstalls it unconditionally.

A restore blocked by trust or hook policy left the client showing an active
goal that nothing drives. Restore now reports `blockedBy`, and history replay
emits a trailing `cleared` card naming the reason. The card is emitted, not
recorded, so a later resume in a trusted folder still restores the goal. It is
emitted from inside replay because `loadSession` batches replay updates into
its response, and a notification sent afterwards would reach the client first.
Gated behind a `HistoryReplayer` option: export and `restoreSessionHistory`
render a transcript rather than resume it, and the export config is a stub that
throws on any method it does not implement.

Transcript payloads are now treated as untrusted. `outputHistoryItems` is
checked with `Array.isArray` before iteration and each entry for being a plain
object before any field is read; a hand-edited record could otherwise throw and
take the whole restore down, skipping the hook while replay still showed the
goal as active.

Also:

- Carry `setAt` across resume instead of restarting the clock, scanning back to
  the run's `set` card when the newest card is a `checking` card (which had no
  `setAt`; they now persist one).
- Refuse to restore an empty condition, as `/goal` does.
- Warn instead of silently no-opping when no chat recording service is present.
- Cap `GET /goals` session probes at 10 in flight.
- Drop `lastTerminal` from the `sessionGoalGet` response and `BridgeSessionGoal`
  — no consumer reads it, and it was returned unprojected.
- `GoalsDialog` keeps the form and the typed condition when creation fails, and
  clears a stale dropped-session count when a reload fails outright.
- Cross-package test pinning `GOAL_CLEAR_KEYWORDS` and `MAX_GOAL_LENGTH` against
  the CLI sources they mirror.

* fix(goals): drop the condition length cap on restore and in the web shell

#6665 removed the 4,000-character cap `/goal` applied when setting a goal, but
the restore path and the Web Shell form still enforced it. After merging main
that split the surfaces: a long condition `/goal` now accepts was persisted as a
`set` card, then refused by `restoreGoalFromHistory` on the next resume and
dropped from the replay entirely — the goal died on reload and the user never
saw a card explaining why.

Remove the cap everywhere rather than reinstate it at set time. A corrupted or
hand-edited transcript can now restore an arbitrarily long condition, but that
is exactly what `/goal` itself permits, so it is no longer a distinct risk. The
empty-condition gate stays: it is the one case that is meaningless rather than
merely large.

- `goalConditionBlockedBy` rejects only an empty condition.
- `HistoryReplayer` no longer skips long goal cards.
- `GoalsDialog` drops the form check and the `maxLength` attribute, which had
  been silently truncating a long condition before the user could submit it.
- `MAX_GOAL_LENGTH` and the now-orphaned `goals.error.tooLong` i18n strings are
  deleted, along with the drift test's length half; the clear-keyword half of
  that test still guards the constant that is genuinely duplicated.

Also drops the `MAX_GOAL_LENGTH` import #6665 left unused in `goalCommand.ts`,
which failed `eslint --max-warnings 0`.

* fix(web-shell): reuse the empty session a failed goal attempt leaves behind

Setting a goal starts a fresh session and then sends `/goal <condition>` into
it. The daemon session is not created by the "new session" step, though —
`clearSession` only detaches and clears local state. `ensureSessionForPrompt`
creates the session lazily inside `sendPrompt`, so a prompt that fails after
the session exists leaves a created-but-empty one behind.

The Goals form keeps the condition and invites a retry, and the retry called
`createNewSession()` again: the empty session from the previous attempt was
abandoned and another created in its place. A user retrying a few times against
a busy daemon ended up with a column of blank chats in the sidebar.

Remember the stranded session and reuse it when it is still the current one,
rather than creating another. Nothing is deleted — a session is only reused
when the failed attempt left it empty and it has not been switched away from.
Once a goal actually lands, the session belongs to it, so the next goal starts
a fresh one as before.

* fix(goals): forget the stranded goal session on leaving the Goals page

Addresses the latest review round on #6561.

The stranded-session reuse added in bee3295aa was only safe while the Goals
page stayed up. Leaving it (Back button) and then talking to that session from
the composer turned it into a real conversation, but the ref still pointed at
it: returning to Goals and setting a goal would reuse it and drop the goal loop
on top of the user's conversation — the exact thing starting a fresh session
exists to prevent. The ref is now cleared whenever the view leaves 'goals', so
reuse can only ever hit a session the failed attempt itself created.

Also:

- `registerGoalHook` rejects a `setAt` in the future, not just a non-finite or
  non-positive one. Every duration downstream is `Date.now() - setAt`, so a
  transcript claiming the goal starts tomorrow rendered negative elapsed times.
- `makeRestoreInnerConfig` gains `isTrustedFolder`. Without it, `goalRestoreBlockedBy`
  threw `config.isTrustedFolder is not a function` on every resume in these
  tests, and `#restoreGoalOnResume` swallowed it — so the goal-gate assertions
  passed through the catch rather than the branch each one names. The
  hooks-disabled test now pins the branch it took, and fails if the config
  regresses.
- The status-bar goal pill names the goal in its accessible label. The visible
  pill is only "◎ /goal active (2m)" and the condition lived solely in `title`,
  a hover tooltip screen readers do not reliably announce.
- `.iconAction` gains a `:focus-visible` rule, matching `.iconButton` in
  DialogShell.module.css; keyboard users had no focus indicator on the
  clear-goal button.
- `GoalsDialog.test.tsx` restores real timers in `afterEach` rather than inline
  per test, so a failing assertion can no longer leak fake timers into the rest
  of the file.
- Tests for the Goals form's Cancel button and for the status-bar pill, neither
  of which had any coverage.

* fix(goals): identify a goal run by its condition, not just its card kinds

Addresses the latest review round on #6561.

`findSetAtOfRun` walked back from the active card for the `setAt` on the `set`
card that opened the run, stopping at any card that was not `set`/`checking`.
That assumed a terminal card always separates two goals, and a transcript is a
file: hand-edited, truncated, or written by a version that did not persist
terminal cards, it can hold two goals back to back. The scan then walked past
the second goal's cards into the first and returned ITS start time, so the
active goal's elapsed time was measured from a goal that had already ended. The
condition is what identifies a run, so the scan now stops when it changes.

Also:

- A malformed condition is reported once on resume, not twice.
  `restoreGoalFromHistory` is the only caller that knows the condition is bad,
  and three of its four callers (the TUI ones) discard the result entirely, so
  it stays the reporter; `#restoreGoalOnResume` no longer adds a second line for
  `condition-invalid`. The env gates were already reporting exactly once.
- Goal-restore stderr can no longer take down a session load. `writeStderrLine`
  reaches `process.stderr.write`, which throws on EPIPE or a closed fd; a throw
  from the catch block would have escaped into `loadSession`, so a best-effort
  restore would fail the very load it promises not to block.
- `isGoalClearCommand` checks the `/goal` prefix instead of assuming it.
  `goalArgOf` returns unrecognised text unchanged, so a bare `"clear"` — an
  ordinary thing to type into a chat box — answered true. Latent today because
  every caller pre-validates the prefix, but the contract was a trap.
- Tests for the throw path reinstalling the terminal observer, and for the Goals
  page opening a goal's session (success and failure), neither of which had any
  coverage.

* fix(web-shell): announce Goals dialog errors and give its buttons a focus ring

Addresses the latest review round on #6561.

The form-validation error and the goal-list load error were painted but never
announced: `role="alert"` puts them in a live region, so a screen-reader user
learns the submit was rejected instead of believing the goal was created, and
learns the list went stale on a poll that failed after the page was already up.
Matches the existing pattern in RewindDialog.

`.primaryButton` / `.secondaryButton` had no `:focus-visible` rule, so keyboard
users tabbing to Set goal / Cancel saw no focus indicator — an inconsistency
with `.iconAction` and `.sessionLink` in the same file. They now take the ring
the form controls already use (`outline: 2px solid var(--primary)`), offset
outwards rather than inset: `.primaryButton` is filled with `--primary`, so an
inset ring in that colour would be invisible on it.

* fix(cli): stop a broken stderr from abandoning a transcript replay

Addresses the latest review round on #6561.

`process.stderr.write` throws on EPIPE or a closed fd — reachable whenever the
reader goes away (`qwen … | head`) or a daemon redirects its stderr. The goal
path writes diagnostics from inside work that must not be destroyed by a failed
diagnostic, and `bee3295aa` only guarded one of the five sites.

The worst of the rest was in `HistoryReplayer`: the "skipping a goal card whose
condition is empty" line sits inside the loop over a record's cards. A throw
there abandoned that record's remaining cards, propagated to the record loop,
and aborted the whole replay — the user lost their transcript because we failed
to complain about one bad card.

Add `writeStderrLineSafe` to stdioHelpers and route the goal path's five sites
through it, replacing the one-off `#warnGoalRestore` wrapper in acpAgent so
there is a single implementation. It is deliberately not the default:
`writeStderrLine` still throws, because most of the CLI wants a broken stderr to
be loud. This variant is for writes that are incidental to real work.

Also adds the first tests for `stdioHelpers`, and covers two untested Goals
dialog behaviours: the Refresh button, and the clear button disabling itself
while its clear is in flight (a double-click otherwise fired two concurrent
clears at the same session).

* fix(web-shell): keep the Goals page mounted across createNewSession

main's `createNewSession` gained a `setMainView('chat')` of its own, fired
synchronously before any await. That silently defeated the Goals handler's
deferred switch: by the time `sendPrompt` rejected, the page — and the form
that renders the error — was already gone, dropping the user into an empty
chat with no explanation. This is the exact failure the deferred switch was
written to prevent; the two changes only had to meet for it to come back.

`createNewSession` takes a `keepView` opt-out, and the Goals handler uses it,
so the page survives until the prompt is admitted. Saving and restoring
`mainView` around the call would also work but flips the view to chat and back,
which the user would see. A test pins the page staying mounted across a failed
submit; it fails if `keepView` stops being honoured.

Also from the same round:

- `registerGoalHook`'s `initialSetAt` guards are now tested — a future
  timestamp, NaN, Infinity, 0 and a negative all fall back to now, and a usable
  value survives. The future case is the one with teeth: `Date.now() - setAt`
  renders a negative elapsed time rather than failing loudly, and nothing
  covered it.
- The goals list carries `role="list"` / `role="listitem"`. They are divs, and
  even a real `<ul>` loses its implicit role under `display: flex` in Safari.
- The open-session button names the action *and* the session. Its visible text
  is only the session name, which says nothing about what activating it does;
  the name stays in the accessible name so it still contains the visible label.
- `.fieldLabel` matches ScheduledTasksDialog's `--muted-foreground`. The two
  dialogs sit side by side and had drifted.

Not taken: deferring `setMainView` in `onOpenSession` until the load resolves.
The sibling `handleOpenSessionFromOverview` switches first by the same pattern,
and `loadSidebarSession` clears the transcript and shows a loading skeleton —
which is the feedback for the common success path. Deferring would leave a
click looking dead until the load lands, and would make Goals diverge from the
Session Overview panel. If we want that behaviour it should change both.

* fix(web-shell): stop the visuals spec asserting a badge #7035 removed

The "Capture web-shell visuals" job fails on this PR at
`screenshots.spec.ts:395`, asserting the sidebar's "Primary" badge is visible:

    Error: expect(locator).toBeVisible() failed
    Error: element(s) not found

Not from this branch. The chain is on main:

- 2026-07-15  #6880 adds the visuals spec, asserting the "Primary" badge —
  correct at the time.
- 2026-07-17  #7035 drops that badge as redundant (the workspace selector's
  checkmark already conveys the default target), removing the `primaryLabel`
  prop and its `<span className={styles.badge}>` render, and updates the *unit*
  test to assert its absence — but leaves this spec asserting it is visible.

The capture job only runs on pull requests (it needs a PR head and a
merge-base), so main never went red for it and the breakage surfaces on the
next PR to merge main — this one.

Assert the badge's absence instead of deleting the check, mirroring the unit
test #7035 added, so a regression re-adding it still fails here.

---------

Co-authored-by: 易良 <1204183885@qq.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-18 08:52:07 +00:00
tanzhenxin
105a818f2f
feat(core): overhaul web_fetch — content fidelity, binary handling, security, and resilience (#7146)
* feat(core): overhaul web_fetch — content fidelity, binary handling, security, and resilience

Rebuild the web_fetch pipeline: honest User-Agent with one-shot retry,
convert-then-truncate HTML→markdown with links preserved, byte-preserving
binary handling with magic-byte sniffing and inline PDF extraction,
model-visible fetch metadata, same-host-only redirects with cross-host
surfacing, 60s full-transfer timeout, 10MB cap, abort wiring, per-session
15-min cache, and ask-by-default egress permissions (the curated docs-host
list only gates verbatim markdown passthrough, never permission).

Claude-Session: https://claude.ai/code/session_01FCyfAqWU39MURn4xS35YGu

* fix(core): address review — hostname-strict blob rewrite, matcher/gate polish, 429 test

- rewriteGitHubBlobUrl now parses the URL and requires the github.com host
  (or www form) plus an /owner/repo/blob/... path: lookalike hosts
  (evil-github.com) and github.com//blob/ appearing in the path or query of
  unrelated URLs no longer trigger the rewrite.
- Preapproved path prefixes match case-insensitively (GitHub owner names
  are case-insensitive), so a casing mismatch no longer costs the markdown
  passthrough.
- Passthrough size gate uses <= to align with the truncation threshold.
- Added 429 retry coverage mirroring the 403 test.

Claude-Session: https://claude.ai/code/session_01FCyfAqWU39MURn4xS35YGu

* fix(core): address second review round — header/mime edge cases, budget & cache coverage

- Malformed redirect Location headers now raise a structured FetchError
  naming the header instead of a bare TypeError.
- RFC 5987 filename* parsing accepts a non-empty language tag.
- application/x-gzip maps to .gz (was falling through to .bin when magic
  bytes were absent).
- @types/turndown moved to devDependencies, matching the other type stubs.
- Coverage folded into existing tests: truncation asserts the needle is
  gone, the disk-budget reserve/rollback calls are asserted, the cache test
  now crosses the TTL, and the PDF test exercises a binary cache hit; two
  small new tests cover the malformed-Location error and the
  Content-Disposition→extension wiring.

Claude-Session: https://claude.ai/code/session_01FCyfAqWU39MURn4xS35YGu

* fix(core): treat printable octet-stream as text + review nits

- application/octet-stream with no magic match and no recognized filename
  extension now runs a cheap text heuristic (NUL bytes / UTF-8 validity on
  the leading 8KB): printable bodies are summarized as text instead of
  persisted as an opaque .bin — real-world .txt/.log/.patch behind
  misconfigured servers regressed to unreadable otherwise. Real binaries
  (NULs or invalid sequences) are unaffected.
- User abort during pdftotext is reported as a cancellation instead of a
  30s timeout (execCommand classifies a signal-killed child as timedOut).
- Dropped the dead .svg branch in the read hint (SVG is always textual and
  never persisted); documented that MAX_CACHEABLE_CHARS is belt-and-braces.
- Coverage: video/ and OpenDocument rows in the binary-type table, EAI_AGAIN
  folded into the parameterized transient-retry test, looksLikeText unit
  test, printable-octet-stream behavioral test.

Claude-Session: https://claude.ai/code/session_01FCyfAqWU39MURn4xS35YGu
2026-07-18 07:03:55 +00:00
易良
878747b1ff
feat(vscode): route logs to the Qwen Code Companion output channel (#7121)
* feat(vscode): route logs to the Champion output channel

* fix(vscode): preserve the output channel name

* refactor(vscode): simplify output logger setup

* fix(vscode): close output logging review gaps

* fix(vscode): harden output log forwarding

* fix(vscode): close webview logging review gaps

* fix(vscode): close output logging review nits

* fix(vscode): preserve log response redaction

* fix(vscode): close output logging review gaps
2026-07-18 06:58:14 +00:00
ytahdn
0ecba4b3c7
feat(web-shell): add skill management pages (#7018)
* feat(web-shell): add skill management pages

* fix(cli): inject GitHub token for skill installs

* test(integration): include skill management capability

* fix(cli): harden skill installation failures

* fix(skills): preserve management compatibility

* fix(cli): isolate skill install transactions

* fix(cli): address skill install review findings

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-07-17 06:42:46 +00:00
qwen-code-ci-bot
bbec6dffb9
chore(release): v0.19.11 (#7042)
* chore(release): v0.19.11

* docs(changelog): sync for v0.19.11

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-16 15:23:33 +00:00
易良
d4c15f05c5
feat(ci): add automated PR failure patrol (#6766)
* feat(ci): add stale failure patrol

* fix(ci): harden failure patrol

* refactor(ci): simplify flaky rerun patrol

* docs(ci): clarify flaky patrol skill boundary

* feat(ci): patrol stale PR failures

* fix(ci): prefilter failed PRs

* fix(ci): isolate patrol classification

* fix(ci): revalidate stale patrol actions

* fix(ci): verify main before branch update

* fix(ci): classify all stale PR failures

* fix(ci): bound patrol batches

* fix(ci): persist patrol failure state

* fix(ci): harden patrol state transitions

* fix(ci): harden stale failure patrol

* fix(ci): continue patrol after expired logs

* fix(ci): tighten patrol guardrails

* fix(ci): preserve failure context in patrol logs

* fix(ci): harden stale patrol closeout

* fix(ci): paginate patrol marker comments

* fix(ci): harden patrol action guards

* test(ci): cover patrol guard rails

* fix(ci): harden patrol marker parsing

* fix(ci): address patrol review followups

* test(ci): cover patrol review edges

* fix(ci): record patrol rerun marker first

* fix(ci): harden patrol review edge cases

* fix(ci): tighten stale failure patrol markers

* refactor(ci): simplify flaky rerun patrol (2838→1258 lines)

- Remove classification guards from actOnDecision (confidence check,
  action enum validation, boundedReason, update_branch multi-guard chain)
- Move classification rules to SKILL.md prompt
- Delete 40 source-code text matching tests, keep 20 behavior tests
- Merge identity job into classify, remove SHA verification
- Change scan sort order from oldest-first to newest-first
- Remove unused functions: writeSkillInputs, failureKey, boundedReason,
  canAct, skillCandidate, mainRunSucceeded

* fix(ci): show gh stderr in top-level error output

When gh CLI returns non-zero exit, execFile rejects with an error whose
.stderr contains the actual GitHub API diagnostic. Previously only one
of stderr or message was shown; now both are printed.

* fix(ci): address patrol review findings

* fix(ci): make stale patrol actions recoverable

* refactor(ci): simplify flaky rerun patrol

* fix(ci): close flaky patrol review gaps

* fix(ci): restore PR failure patrol actions

* fix(ci): harden failure patrol scanning

* fix(ci): address patrol review follow-ups

* fix(ci): harden patrol parsing and coverage

* fix(ci): classify failures against PR changes

* fix(ci): harden patrol script input handling

* fix(ci): remove unsafe auto branch update

* test(ci): exercise patrol action limit

* fix(ci): bind patrol actions to current evidence

* fix(ci): count patrol actions per PR

* fix(ci): redact quoted secret labels

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-15 00:51:11 +00:00
qwen-code-ci-bot
42d7d28d11
chore(release): v0.19.10 (#6855)
* chore(release): v0.19.10

* docs(changelog): sync for v0.19.10

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-14 05:12:22 +00:00
jinye
fea3ab3854
feat(serve): add extension management v2 (#6825)
* feat(cli): workspace-qualified extensions REST (daemon multi-workspace)

Mirror the daemon extension-management REST surface to per-workspace routes, reusing the Phase 3 runtime resolver and trust gate. Extract a per-workspace extensions controller so the primary workspace shares one install queue, operation history, and status cache across the legacy and workspace-qualified routes. Reads resolve the target runtime only; mutations require a trusted workspace. Advertise a new baseline capability so clients can discover the surface, and add matching SDK client methods.

Refs #6378.

* qwen: address PR review feedback (#6638)

Align the new extensions controller file's copyright year with the other new files added in this change.

* qwen: address PR review feedback (#6638)

Redact credentials from the extension source on the two success-path fan-outs (session refresh and refresh-failure broadcast), matching the operation record and failure broadcast. Document the non-cancellation semantics of the extension timeout wrapper.

* qwen: address PR review feedback (#6638)

Share the queue-full sentinel message via an exported constant so the throw site (controller) and the 429 match site (routes) cannot drift after the module split. Include the bound workspace in the extension operation log prefixes so concurrent per-workspace controllers are distinguishable in stderr.

* feat(cli): add concurrent extension preparation

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

* fix(cli): remove redundant extension context build

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

* codex: address PR review feedback (#6638)

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

* codex: address PR review feedback (#6638)

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

* fix(cli): address extension review feedback

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

* fix(extensions): address final review feedback

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

* fix(extensions): address latest review feedback

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

* codex: address PR review feedback (#6638)

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

* fix(core): reject links in npm extension archives

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

* fix(core): limit npm extension archive downloads

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

* fix(extensions): address review follow-ups

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

* fix(extensions): address latest review feedback

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

* fix(extensions): release rejected operation slots

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

* fix(extensions): address operation review feedback

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

* fix(extensions): align archive handling contracts

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

* fix(extensions): preserve watcher generation state

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

* docs(extensions): align management contracts

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

* fix(sdk): bound extension operation polls

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

* test(core): cover forged prepared commits

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

* test(core): assert activation generation increment

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

* fix(extensions): close archive and polling gaps

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

* fix(cli): retry suppressed extension generations

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

* test(cli): cover archive URL extension updates

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

* fix(sdk): preserve unbounded operation waits

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

* fix(core): share npm redirect download deadline

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

* fix(core): preserve extension reload diagnostics

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

* fix(core): preserve installed Claude plugin paths

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

* fix(cli): return committed activation state

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

* fix(cli): preserve extension preparation errors

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

* fix(core): validate extension setting env vars

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

* fix(cli): target extension reconciliation

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

* codex: address PR review feedback (#6638)

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

* codex: address PR review feedback (#6638)

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

* codex: address PR review feedback (#6638)

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

* codex: address PR review feedback (#6638)

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

* codex: address PR review feedback (#6638)

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

* codex: address PR review feedback (#6638)

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

* codex: address PR review feedback (#6638)

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

* test(cli): cover resultless legacy commit warnings

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

* fix(cli): retain suppressed extension generations

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

* fix(cli): record legacy runtime reconciliation

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

* fix(cli): validate extension clients by runtime

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

* fix(cli): record workspace activation refresh

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

* test(cli): stop extension reconcilers after cases

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

* fix(cli): resolve global runtimes at reconciliation

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

* fix(cli): reconcile newly registered runtimes

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

* fix(cli): prevent overlapping runtime reconciliation

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

* fix(cli): dispose late runtime apps during shutdown

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

* fix(core): keep projection repair best effort

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

* fix(core): preserve committed store results

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

* fix(core): quarantine corrupt store journals

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

* fix(core): harden npm download redirects

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

* fix(extensions): address review edge cases

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

* fix(extensions): honor cancellation between preparation stages

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

* fix(extensions): retry prepared cleanup failures

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

* test(extensions): cover committed artifact recovery boundary

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

* fix(serve): release extension refresh queue on timeout

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

* codex: address PR review feedback (#6638)

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

* codex: address PR review feedback (#6638)

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

* codex: address PR review feedback (#6638)

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

* codex: address PR review feedback (#6638)

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

* fix: address extension review feedback

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

* codex: address PR review feedback (#6638)

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

* codex: address PR review feedback (#6638)

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

* codex: address PR review feedback (#6638)

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

* codex: address PR review feedback (#6638)

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

* fix(core): reconcile extension store compatibility state

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

* fix(core): bound npm redirects and isolate extension tests

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

* fix(core): make extension uninstall store-authoritative

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

* fix(core): defer prepared extension secret mutations

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

* fix(core): validate staged extensions before commit

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

* fix(core): enforce public extension network policy

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

* fix(core): handle extension response failures

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

* fix(extensions): surface committed refresh warnings

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

* fix(cli): guard timer unref calls

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

* fix(extensions): release commit lane after durable writes

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

* test(serve): update mutation callback assertions

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

* fix(serve): refresh live extension instructions

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

* fix(extensions): address latest review feedback

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

* fix(extensions): address follow-up review findings

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

* fix(extensions): address remaining activation feedback

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

* fix(serve): preserve preparation queue status

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

* fix(extensions): enforce network request deadlines

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

* docs(serve): clarify single-workspace capabilities

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

* fix(extensions): guard deferred settings commit

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

* fix(extensions): cancel archive extraction

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

* fix(extensions): harden refresh recovery

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

* fix(serve): serialize extension reconciliation

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

* fix(extensions): address post-commit review feedback

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

* codex: address PR review feedback (#6825)

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

* codex: address PR review feedback (#6825)

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

* codex: address PR review feedback (#6825)

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

* codex: fix CI failure on PR #6825

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

* codex: fix CI failure on PR #6825

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

* codex: address PR review feedback (#6825)

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

* fix: address critical PR review feedback

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

* fix(cli): bound legacy extension update checks

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

* fix(acp): deduplicate extension refresh requests

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

* codex: address PR review feedback (#6825)

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

* codex: address PR review feedback (#6825)

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

* codex: address PR review feedback (#6825)

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

* codex: address PR review feedback (#6825)

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

* fix(sdk): update browser bundle budget after main merge

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>
2026-07-14 03:30:47 +00:00
Shaojin Wen
71c7e448f0
feat(web-shell): editable user-scope settings and in-panel model management (#6768)
* feat(web-shell): editable user-scope settings and in-panel model management

Make the Settings panel able to manage ~/.qwen/settings.json (user scope),
and add model management inside the panel's Model category.

User scope: the User tab was read-only; enable writing user-scope settings
end-to-end (route allows the user scope, and the client/UI thread it through),
so the same structured controls edit ~/.qwen/settings.json.

Model management: list configured models (grouped by provider), set the
current model, add a model (reusing the existing provider-setup wizard), and
delete a model. Delete is the only new backend surface: DELETE /workspace/models
rewrites modelProviders in the owning scope, empties (rather than drops) a
provider whose last model is removed so the format-preserving settings writer
clears it cleanly, and clears model.name when the active model is deleted.

Voice Model and Model Fallbacks now use pickers instead of free text: Voice
Model reuses the voice picker; Model Fallbacks opens a multi-select (up to 3,
ordered) whose value round-trips through the comma-separated setting.

* fix(web-shell): label model sub-dialog buttons "Select" not "Edit"

Fast/Vision/Voice Model and Model Fallbacks open a picker, so their
empty-state button now reads "Select" instead of "Edit", which wrongly
implied free-text editing.

* fix(sdk): export DaemonModelDelete{Request,Result} from daemon entry

The DELETE /workspace/models types were added to daemon types but not
re-exported from @qwen-code/sdk/daemon, so consumers resolving the built
dist (webui's dts rollup in CI) failed with TS2305.

* fix(web-shell): address automated review of model management

- reset modelSettingScope when the Add Model (auth) dialog closes too, not
  only the model picker / fallbacks dialog (Critical)
- delete: don't surface a reload failure as "delete failed"
- ModelFallbacksDialog: drop role=listbox/option (no arrow-key nav) for
  aria-pressed toggle buttons matching the click-only interaction
- add :focus-visible outlines to model-management and fallbacks buttons
- mock daemon: handle DELETE /workspace/models
- document first-id-match-wins in removeModelFromProviders
- tests: cover 500 path, broadcast assertions, parseTarget validation codes,
  active-model clearing with a pinned baseUrl, and scope the runtime-model
  no-delete assertion to that row

* fix(serve): keep workspace-qualified settings route workspace-only

Widening VALID_WRITE_SCOPES to include 'user' for the primary
/workspace/settings route also loosened the trust-gated
/workspaces/:workspace/settings route, breaking its deliberate
"reject user scope" contract. Give the qualified route its own
workspace-only scope set; the primary route keeps user scope.

* fix(web-shell): address second review round of model management

- isActiveModelSelection: when the active model is pinned to a baseUrl, an
  id-only delete no longer clears it (may have removed a different variant)
- DELETE /workspace/models: on a partial multi-key persist failure, broadcast
  the committed writes before returning 500 (matches workspace-voice)
- model pickers (fast/vision/voice + fallbacks) read the value for the scope
  being edited, so the User tab no longer shows/clears workspace values
- voice sub-dialog: functional setState so a late loadProviders().then() can't
  clobber a picker the user opened meanwhile
- ModelManagementSection cancel button honors the busy state
- doc fix (emptied provider keys are kept as empty arrays), plus tests for the
  baseUrl-asymmetry and partial-persist paths

* fix(web-shell): address third review round of model management

- DELETE /workspace/models returns a structured partial-persist response
  ({ code: 'partial_persist_error', committedKeys }) so callers can reconcile
- scrub the deleted model id out of modelFallbacks so no dangling reference
  remains; trim padded request fields before matching
- reload workspace settings after a delete so a cleared active model / scrubbed
  fallback isn't shown stale
- narrow the workspace-qualified SDK client back to scope: 'workspace' (that
  route is workspace-only); drop the now-dead qualified scope ternary
- remove the unused providerKey from RemoveModelResult

* fix(web-shell): address fourth review round of model management

- handleFallbacksConfirm isolates the settings-reload failure from the
  save-failed toast (a reload reject no longer looks like a save failure)
- readScopedModelSetting returns only the edited scope's value (no effective
  fallback), so the User tab doesn't show/appear-to-clear inherited values
- Add Model button honors the busy state
- widen DaemonSettingUpdateResult.scope to 'workspace' | 'user' to match the
  server echo
- tests: workspace-scope owner path, modelFallbacks scrub broadcast,
  baseModelId current-match, and a delete target without baseUrl

* fix(web-shell): use theme --error-color for model delete buttons

Replace hardcoded #d64545 with var(--error-color) so the destructive model
buttons match the per-theme error color used across the web-shell (dark
#fc8181 / light #c0362c) instead of a fixed mid-red.

* fix(web-shell): address qwen /review self-review of model management

- surface requiresRestart for modelFallbacks changes: handleFallbacksConfirm
  and handleDeleteModel show the restart notice, and DELETE /workspace/models
  reports requiresRestart when a committed write targets a restart-required key
- only scrub a deleted model from modelFallbacks when no other provider still
  configures the same bare id (fallbacks are bare-id, so a same-id model under
  another provider may still want that fallback)
- widen DaemonModelDeleteResult with requiresRestart; add tests for the
  keep-fallback case and the requiresRestart response

* fix(web-shell): address fifth review round of model management

Backend (mixed-scope correctness for model deletion):
- Clear the active model selection in every writable scope whose own
  selection names the deleted model, comparing against the removed
  entry's stored (unsanitized) baseUrl so a credential-bearing URL is
  still recognized after the providers status sanitizes it.
- Scrub modelFallbacks in its own owning scope rather than the
  modelProviders owner scope; the two are independently scoped.
- removeModelFromProviders now reports removedBaseUrl.

CLI:
- /language ui accepts --project/--global so the settings panel can
  persist a UI-language change to the selected scope while still
  switching the daemon's live locale.

Web-shell:
- Fast-model picker forwards the selected scope via --project/--global.
- Theme/Language controls display the selected scope's value; Language
  persists through the scoped command.
- The model-management "current" badge uses a single-winner,
  endpoint-aware match so a bare current id no longer marks every
  same-base-id row current.
- Serialize Set current through the shared busy flag; reset the recorded
  scope if voice-provider loading rejects.

Tests: mixed-scope route cases, strict-mutation/client-id harness
assertions, SDK setWorkspaceSetting/deleteModel transport tests, a
useDaemonProviders hook test, language scope-flag tests, and dom tests
for the current-badge and fallbacks normalization.

* fix(web-shell): address second qwen /review self-review of model management

- onSubDialog now records the model persist scope per model sub-dialog
  (fast/vision/voice/fallbacks) and no longer for the non-model
  approvalMode dialog — the reset effect is gated on the dialog/fallback/
  auth flags, so it never covers approvalMode and would otherwise leave a
  stale scope for a later command-launched picker. (The flagged voice
  "scope-reset race" does not actually occur: that same effect gating means
  no render between the synchronous scope set and the picker opening
  re-runs it — but the scope handling is now explicit per branch.)
- Add an aria-label to the delete-confirm Cancel button so screen readers
  can tell which model's confirmation is being cancelled.

Tests:
- Unit tests for getWritableScopes / getOwnKeyScope (trust + per-scope
  ownership, incl. explicitly-set falsy values).
- Fast-model User-tab test asserting the /model --fast --global flag.
- Theme scoped-read test: the control shows the selected scope's value,
  not the effective merge.
- Fix the useDaemonProviders mock: `current` is a provider-current object,
  not a bare id.

* fix(web-shell): fix voice-picker scope race and provider memoization

- Voice model scope race: the voice picker opens asynchronously (after
  loadProviders), so recording the persist scope synchronously up front
  let it be clobbered — if the user opened and closed another picker while
  loadProviders was in flight, the reset effect reset the scope and the
  voice model persisted to the wrong scope. Now the scope is captured from
  the click and applied together with the open, guarded by a
  modelDialogMode ref so it only opens (and sets scope) when no other
  surface opened meanwhile. (Vision/fast are synchronous and unaffected.)
- Depend on the stable `reload` fn (extracted as reloadProviders) instead
  of the fresh-every-render providersState object, so handleDeleteModel /
  handleCloseAuthDialog aren't recreated each render.
- Add role="group" + aria-label to the model-fallbacks option list so
  screen readers announce it as a labeled multi-select group (+ test).

* fix(web-shell): address review round on model-management follow-up

Frontend:
- Remove the redundant `data-keyboard-scope` from ModelFallbacksDialog's
  inner div — the wrapping DialogShell already provides it, and the extra
  scope became the last match in DialogShell's close cleanup, whose
  role="dialog" lookup then failed and dropped focus when the dialog
  closed while another was stacked.
- Voice picker open-guard now also checks the fallbacks/auth dialog flags
  (via refs), matching "no other surface opened meanwhile" so it can't
  open on top of a dialog opened while providers were loading.
- Provider group key includes the index (two providers can share an
  authType) to avoid duplicate-key reconciliation.
- handleCloseAuthDialog logs a failed provider reload instead of
  swallowing it, like the sibling handlers.
- Model-fallbacks options at the max show a `title` explaining the limit
  (new localized string).

Backend:
- Split the DELETE /workspace/models baseUrl validation so a too-long
  value reports a length error, not "must be a string".

Tests:
- Workspace-scoped language change (`/language ui --project`).
- Security-sensitive key (tools.approvalMode) rejected at user scope.
- baseUrl length-limit rejection.
- Model-fallbacks Cancel → onClose; delete-confirm Cancel path restores
  the Delete button; fallbacks accessible grouping already covered.

* fix(web-shell): a11y + robustness follow-ups on model management

- Model-fallbacks max-limit options use aria-disabled instead of the
  native disabled attribute so they stay hoverable and can surface the
  "limit reached" title (disabled buttons fire no events); the toggle
  handler already no-ops at the max. CSS updated to match.
- Inline delete confirmation dismisses on Escape (the conventional
  gesture) so keyboard users need not Tab to Cancel.
- scopeToWire throws on an unexpected SettingScope instead of silently
  reporting it as 'user'.
- Re-export DaemonWorkspaceProviderCurrent from the webui daemon facade
  alongside the sibling provider types.
2026-07-13 14:44:54 +00:00
ytahdn
39e3bc0f40
feat(web-shell): add shadcn UI foundation (#6760)
* feat(web-shell): add shadcn UI foundation

* fix(web-shell): address shadcn foundation review

* revert(web-shell): keep generated shadcn components unchanged

* fix(web-shell): activate dark theme and vertical navigation

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-07-12 13:51:52 +00:00
jinye
51d4ce48db
feat(serve): persist dynamic workspace registrations (#6716)
* feat(serve): persist dynamic workspace registrations

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

* codex: address PR review feedback (#6716)

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

* codex: address PR review feedback (#6716)

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

* codex: address PR review feedback (#6716)

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

* codex: address PR review feedback (#6716)

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

* codex: address PR review feedback (#6716)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-11 16:49:40 +00:00
qwen-code-ci-bot
40ed6b21d7
chore(release): v0.19.9 (#6693)
* chore(release): v0.19.9

* docs(changelog): sync for v0.19.9

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-11 00:35:15 +00:00
ytahdn
0ef3a76bda
feat(web-shell): add artifact right panel (#6591)
* feat(web-shell): add artifact right panel

* fix(web-shell): address artifact panel review feedback

* fix(web-shell): handle artifact panel review edge cases

* fix(web-shell): tighten scheduled task parsing

* fix(web-shell): address artifact panel review followups

* fix(web-shell): guard large file diff stats

* fix(web-shell): address review panel suggestions

* test(webui): stabilize heartbeat prompt cleanup test

* fix(web-shell): address artifact review refresh issues

* test(web-shell): stabilize ChatPane artifact hook mock

* fix(web-shell): clear stale session artifacts while loading

* fix(web-shell): preserve artifact tabs during refresh

* fix(web-shell): address artifact review followups

* fix(web-shell): respect workspace cwd for artifact outputs

* fix(web-shell): scope artifact panel actions to pane

* fix(web-shell): resolve split pane merge conflict

* fix(web-shell): clear stale artifact panel state

* fix(web-shell): preserve leading turn outputs

* fix(web-shell): tighten turn output selectors

* fix(web-shell): harden artifact preview sanitizer

* fix(web-shell): address artifact panel review regressions

* fix(web-shell): reconcile split pane artifact snapshots

* fix(web-shell): clear pane artifacts on session switch

* fix(web-shell): clear stale right panel snapshots

* fix(web-shell): repair scheduled task hint string

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-11 00:13:49 +00:00
tanzhenxin
7a9ee09f49
fix(core): honor NO_PROXY for model requests (#6640) 2026-07-10 10:41:04 +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
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
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
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
qwen-code-ci-bot
4e3fd29781
chore(release): v0.19.6 (#6280)
* chore(release): v0.19.6

* docs(changelog): sync for v0.19.6

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-03 16:37:54 +00:00
Shaojin Wen
68ff698cd2
feat(web-shell): improve slash command discovery (taller menu, group counts, fuzzy search) (#6267)
* feat(web-shell): show more slash commands with category headers

The slash-command menu capped its visible height at exactly four rows, so
with 40+ merged commands users had to scroll a thin list to find anything,
and the built-in custom/skill/system grouping was only a faint 1px divider
with no label.

Raise the cap to min(12 rows, 40vh) and render the category name as a visible
header at each group boundary (custom / skill / system), keeping the divider
between groups. Sub-command menus are ungrouped and unchanged.

* feat(web-shell): fuzzy-match slash commands and show per-group counts

Typing in the slash menu now fuzzy-ranks commands with the same fzf engine the
TUI uses, so abbreviated input like "mdl" finds "model" and "arf" finds
"agent-reproduce-feature" — substring matching alone could not. An empty query
still browses the category-ordered list; a non-empty query switches to a flat
relevance-ranked list (headers are dropped since results interleave categories).

Each category header also shows how many commands the group holds (e.g. "Skill
commands  28"), so the volume hidden below the fold is visible at a glance.

The fzf index is built once per command set (keyed on the array identity) and
falls back to substring filtering if construction fails.

* refactor(web-shell): address slash menu review feedback

- Extract the section header/divider boundary logic into a pure
  `planSlashSectionRows` helper and unit-test it (headers at group
  boundaries, first row header without a divider, no repeated headers for
  adjacent duplicate sections, per-group counts). This also moves the
  section-count computation past the `!anchorRect` early return so it no
  longer runs on first render.
- Simplify `--slash-panel-max-height` to a round `min(460px, 45vh)` instead
  of a `12 * rowHeight` formula that ignored header/divider overhead and so
  showed only ~9-10 rows; the panel now shows ~12-13 rows.
- Log a warning when fzf fuzzy search throws before falling back to
  substring matching, so a silent failure is diagnosable.
- Add a completion test for the zero-match case returning null.
2026-07-03 14:56:06 +00:00
顾盼
23c6c7032a
feat(mobile-mcp): vendor mobile-mcp with opt-in 0-1000 relative coordinates (#6235)
* Squashed 'packages/mobile-mcp/' content from commit c5d7d27fd

git-subtree-dir: packages/mobile-mcp
git-subtree-split: c5d7d27fd61e4762e15ae4b1c68b6c011be88bb7

* feat(mobile-mcp): vendor mobile-mcp with opt-in 0-1000 relative coordinates

Fork mobile-next/mobile-mcp (v0.0.61) into packages/mobile-mcp/ via git
subtree, renamed to @qwen-code/mobile-mcp with the following additions:

Relative coordinate shim (src/coord-norm.ts):
- MOBILE_MCP_COORDINATE_SPACE=1 enables 0-1000 normalized coordinates
- MOBILE_MCP_COORDINATE_SCALE configurable (default 1000, 999 for mobile_use)
- Input denormalization for click/double_tap/long_press/swipe
- Output normalization for list_elements and get_screen_size
- Tool description rewriting when enabled
- Default off = zero behavior change

Android enhancements:
- mobile_install_app: -r/-g/-d/-t install flags (Android only)
- mobile_ui_dump: full UIAutomator XML hierarchy dump
- mobile_adb_pull / mobile_adb_push: file transfer via ADB

Infrastructure:
- cd-mobile-mcp.yml: npm publish workflow (tag mobile-mcp-v*)
- scripts/sync-from-upstream.sh: git subtree pull for upstream sync
- .vendored-from / .vendored-patches.md: vendoring metadata
- Upstream telemetry disabled by default
- eslint.config.js: exclude packages/mobile-mcp from root lint

* chore(mobile-mcp): update package-lock.json for workspace dependencies

* fix(mobile-mcp): quote all YAML strings to pass yamllint

* fix(mobile-mcp): fix cd workflow yaml to pass both yamllint and actionlint

* fix(mobile-mcp): address review findings on our additions

- ensureScreenSize: log warning instead of silent failure (#4)
- invalidateScreenSize on orientation change (#5)
- adb_push: path.posix.resolve to prevent /sdcard/ traversal (#6)
- adb_pull: readOnlyHint → destructiveHint (writes local file) (#9)
- adb_push: remove validateOutputPath on read-source local_path (#11)
- normalizeElementResult: log error instead of bare catch (#16)
- rewriteDescription: remove dead duplicate regex (#17)
- cd workflow: add test step between build and publish (#19)

* fix(mobile-mcp): update server.json identity and fix package.json main entrypoint

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-03 08:50:27 +00:00
qwen-code-ci-bot
2126474c28
chore(release): v0.19.5 (#6194)
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
* chore(release): v0.19.5

* docs(changelog): sync for v0.19.5

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-02 13:38:46 +00:00
易良
489bea9771
fix(release): reduce npm package scan triggers (#6164)
* fix(release): reduce npm package scan triggers

* fix(release): remove browser MCP dev dependencies

* test(serve): stabilize CDP tunnel acceptance startup

* fix(serve): remove unused chrome devtools MCP helper

* fix(serve): remove unused path import

* fix(serve): address CDP MCP review comments
2026-07-02 08:06:43 +00:00
qwen-code-ci-bot
f3ea17bf43
chore(release): v0.19.4 (#6132)
* chore(release): v0.19.4

* docs(changelog): sync for v0.19.4

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-01 13:13:47 +00:00
jinye
b4fe43741a
feat(daemon): Add session archive support (#6058)
* feat(daemon): add session archive support

Add active versus archived daemon session storage, archive and unarchive APIs, strict live-session close handling, ACP and SDK support, and coverage for archive listing, load rejection, deletion, and route mapping.

Closes #6057

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

* codex: fix CI failure on PR #6058

Update the no-AK integration capability baseline to include the new session_archive capability added by this PR.

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

* codex: address PR review feedback (#6058)

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

* fix(daemon): preserve live session on strict close failure

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

* fix(daemon): serialize session delete with archive transitions

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

* refactor(daemon): share session archive orchestration

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

* fix(daemon): clean up strict close failures

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

* docs(daemon): clarify archive recovery tradeoffs

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

* fix(daemon): log session archive outcomes

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

* fix(daemon): parallelize archive session closes

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

* fix(daemon): serialize archive restore races

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

* fix(daemon): satisfy archive lint checks

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

* fix(daemon): keep strict close retryable

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

* fix(daemon): distinguish archive conflicts

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

* fix(core): warn on unreadable session heads

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

* docs(core): document active-only session helpers

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

* fix(daemon): tighten archive review edges

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

* fix(daemon): allow concurrent session restores during archive gate

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

* fix(daemon): avoid archive head reads on session restore

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

* codex: address PR review feedback (#6058)

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

* fix(sdk): account for session archive bundle size

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

* codex: address archive gate review (#6058)

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

* codex: address archive review follow-ups (#6058)

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

* codex: address ACP archive review feedback (#6058)

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

* fix(daemon): Address session archive review feedback

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

* test(daemon): Cover close ownership restoration

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

* fix(daemon): Address archive review feedback

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

* test(daemon): Cover ACP close prompt fallback

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

* test(daemon): Cover archive close channel loss

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

* fix(daemon): Address archive follow-up review

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

* fix(daemon): Return archiving conflict for delete gate

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

* fix(core): Treat unreadable archived ids as occupied

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

* refactor(daemon): Share session delete orchestration

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

* fix(daemon): Address archive review feedback

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

* fix(deps): Clear critical runtime audit failures

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

* fix(deps): Sync runtime dependency ranges with main

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-01 16:24:26 +08:00
Gaurav
fc184f20e5
fix(deps): clear critical runtime audit findings (#6065)
* fix(deps): clear critical runtime audit findings

* fix(core): allow intentional worktree hooks path setup
2026-07-01 05:28:32 +00:00
jinye
cf6323bfb5
feat(cli): Add daemon-managed channel worker for serve --channel (#6031)
* feat(cli): add daemon-managed channel worker

* codex: address PR review feedback (#5978)

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

* codex: address PR review feedback (#6031)

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

* codex: address PR review feedback (#6031)

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

* codex: address PR review feedback (#6031)

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

* fix(cli): harden serve channel worker lifecycle

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

* fix(cli): cover channel worker edge cases

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

* fix(cli): address channel worker review followups

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

* fix(cli): clear channel pidfile after worker exit

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

* fix(cli): address serve channel review feedback

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

* fix(cli): harden serve channel worker review issues

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

* fix(cli): preserve channel worker exit errors

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

* fix(cli): harden daemon channel worker startup

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

* fix(cli): address channel worker review cleanup

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

* fix(cli): track channel worker exit explicitly

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

* codex: address PR review feedback (#6031)

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

* codex: address daemon worker startup review (#6031)

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

* codex: address PR review feedback (#6031)

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

* codex: address daemon worker disconnect review (#6031)

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

* fix(cli): address serve channel review feedback

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

* fix(cli): address channel worker review feedback

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-01 01:40:54 +00:00
易良
5581424b6b
feat(browser-ext): revive Chrome extension via daemon-direct architecture (#5777)
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(chrome-qwen-bridge): 🔥 init chrome qwen code bridge

* chore(chrome-qwen-bridge): connect

* chore(chrome-qwen-bridge): connect & them

* chore(chrome-qwen-bridge): connect & them

* chore(chrome-qwen-bridge): wip  use chat ui

* chore(chrome-qwen-bridge): wip  use chat ui

* wip

* refactor(chrome-extension): rename chrome-qwen-bridge package to chrome-extension

* feat(chrome-extension): enhance network monitoring with webRequest API

Replace the existing network monitoring implementation with a more comprehensive solution that combines both webRequest and debugger APIs for broader coverage. The new implementation:

- Uses chrome.webRequest.onBeforeRequest to capture all outgoing requests
- Uses chrome.webRequest.onCompleted to capture completed responses
- Uses chrome.webRequest.onErrorOccurred to capture failed requests
- Retains debugger API integration for detailed network information
- Implements memory management with maximum 1000 logs per tab
- Adds proper initialization and cleanup for each tab
- Ensures graceful handling of debugger attachment failures
- Provides more reliable network activity capture across all tabs

This enhancement significantly improves the reliability and coverage of network monitoring functionality in the Chrome extension.

* refactor(chrome-extension): reorganize directory structure for better maintainability

This commit reorganizes the entire Chrome extension package structure for improved maintainability and clarity:

- Move all source files to `src/` directory (background, content, sidepanel)
- Move build configurations to `config/` directory
- Move documentation to `docs/` directory with proper categorization
- Move all script files to `scripts/` directory
- Move native-host specific files to appropriate subdirectories (`src`, `scripts`, `config`)
- Update package.json scripts to reflect new file locations
- Add comprehensive documentation files (debugging, development, architecture, API reference)
- Maintain all functionality while improving project organization

The reorganization separates source code from build output, centralizes documentation, and creates a clear separation of concerns making the project more maintainable and easier for developers to navigate.

* feat(chrome-extension): enhance native host communication and network logging

- Add troubleshooting documentation for native host setup issues
- Improve native host logging to home directory with fallback to tmp
- Enhance network logging in service worker with response body capture
- Update scripts to properly reference host.js from correct path
- Increase timeout for MCP session creation and long prompts from 3 to 5 minutes
- Add getConsoleLogs functionality to sidepanel for content script capture
- Improve browser-mcp-server network logs aggregation by request ID
- Update icon assets and improve manifest configuration

refactor(chrome-extension): consolidate host.js entry point and improve path resolution

- Create unified host.js entry point that delegates to src/host.js
- Improve path resolution for host scripts in installer and runner scripts
- Add proper path existence checks for browser-mcp-server.js
- Support running from different directory structures

style(chrome-extension): improve TypeScript type safety and error handling

- Add proper type definitions for message handling in side panel
- Add null checks and error handling for message parsing
- Improve React component callback implementations

* refactor(chrome-extension): redesign build workflow

* fix(chrome-extension): resolve ESLint errors in native host, service worker, and content script

- Fix 'Unexpected lexical declaration in case block' by wrapping switch cases in blocks
- Fix 'Unexpected constant truthiness on the left-hand side of a || expression' by using conditional patterns
- Fix unused variable errors by properly using catch error parameters or adding logging
- Fix 'document' and 'window' not defined errors in service worker with proper global declarations
- Add eslint-disable comments where appropriate for globals used in specific contexts

* feat(chrome-extension): enhance native host with browser MCP tools and event streaming

- Add new browser MCP tools: browser_click, browser_click_text, browser_run_js, browser_fill_form_auto
- Implement SSE (Server-Sent Events) for improved event streaming instead of long-polling
- Add daemon script for running the bridge host in background
- Enhance documentation with MCP notes and updated README
- Add multiple executable binaries to package.json: chrome-browser-mcp, qwen-bridge-host
- Improve error handling and event processing in the native host
- Add debouncing mechanism for stream end events in service worker
- Update timeout for MCP discovery to accommodate slower startup

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* fix(chrome-extension): use trusted cwd for MCP tool discovery

The root cause of MCP tools not being recognized by Qwen CLI was that
the cwd (current working directory) was defaulting to '/' (root directory).

In Qwen CLI's MCP discovery logic, there's a security check:
  if (!cliConfig.isTrustedFolder()) {
    return;  // Skip MCP tool discovery
  }

The root directory '/' is not a trusted folder, so MCP tools were
silently not being discovered at all.

Changes:
- host.js: Default to $HOME instead of process.cwd() for start_qwen
- service-worker.js: Remove '/' fallback, let host.js handle default

This ensures browser MCP tools (browser_read_page, browser_click, etc.)
are properly discovered and available to the model.

* fix(core): validate MCP entry script existence before connection

Add pre-flight check to verify that stdio-based MCP server entry scripts
exist on disk before attempting connection. This prevents silent failures
and provides clear error messages for misconfigured MCP servers.

* fix(chrome-extension): enhance native host path resolution and port config

- Add QWEN_BROWSER_MCP_SERVER_PATH env override for custom installations
- Expand candidate search paths for browser-mcp-server.js discovery
- Add detailed logging when MCP server script is not found
- Support BRIDGE_PORT env variable for HTTP API server configuration

* fix(chrome-extension): improve browser MCP server reliability and debugging

- Add comprehensive debug logging for bridge health checks and host spawn
- Support BROWSER_MCP_NO_SPAWN env to disable automatic host.js spawning
- Handle bridge unavailability gracefully with clear error messages
- Add raw JSON mode fallback for clients without Content-Length framing
- Capture and log host.js stdout/stderr instead of inheriting stdio
- Add pre-flight bridge check at startup for better diagnostics
- Handle each bridge call failure with proper error responses

* chore(chrome-extension): add debug wrapper script for MCP server

Add cbmcp-wrapper.sh to help diagnose MCP server invocation issues.
The wrapper logs invocation details and stderr to /tmp/cbmcp.log,
making it easier to debug when Qwen CLI spawns the MCP server.

* docs(chrome-extension): add MCP/Bridge troubleshooting guide

Document common failure scenarios when MCP bridge shows as Disconnected:
- EPERM errors when spawning host.js cannot bind to port
- Content-Length framing issues during MCP handshake
- Step-by-step troubleshooting with flow diagrams
- Manual bridge setup with BROWSER_MCP_NO_SPAWN workaround

* build(chrome-extension): 浏览器插件 mcp 构建优化

* docs(chrome-extension): update docs

* fix(chrome-extension): 解决CDP响应体获取和权限请求处理问题

* feat(mcp-chrome-integration): add MCP Chrome browser extension integration

Add complete Chrome extension with native messaging host for MCP integration:
- Chrome extension with sidepanel UI, service worker, and content script
- Native server with agent engines (Claude/Codex), session management, and tool bridge
- Shared packages for types, tools, and node specifications
- Documentation and build scripts

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

* chore(mcp-chrome-integration): refactor

* feat(chrome-extension): 切换到HTTP后端代理并更新构建配置

* refactor(mcp-chrome-integration): build

* wip

* wip chrome extension

* chore: ignore .worktrees

* docs: plan sidepanel component removal

* refactor(sidepanel): remove local components

* feat(chrome-extension): add native messaging ACP client and protocol support

- Add ACP client for native messaging communication
- Add file handler for local file access via native host
- Add protocol definitions for ACP communication
- Archive old documentation files
- Update integration status and protocol documentation

* fix(chrome-extension): use GenericToolCall from @qwen-code/webui

- Replace non-existent local ToolCallCard import with GenericToolCall
- Import from @qwen-code/webui package instead of local path
- Fix component props to match GenericToolCall interface
- Remove unused ToolCallData import

* refactor(mcp-chrome-integration): split large files and fix ESLint errors

- Split tools.ts (1554 lines) into 8 schema files by functionality
- Split native-messaging.ts (1533 lines) into 6 modules
- Split doctor.ts (1099 lines) into 8 modules
- Split content-script.ts (1055 lines) into 5 modules
- Add Qwen Team license headers to all new files
- Remove unused tool names (SEARCH_TABS_CONTENT, SEND_COMMAND_TO_INJECT_SCRIPT, USERSCRIPT, RECORD_REPLAY)
- Fix ESLint errors: no-require-imports, no-explicit-any, no-unused-vars, prefer-const
- Add archive/ directory to eslint ignore patterns

* 调试 MCP 工具成功

* refactor: revert unnecessary formatting changes and clean up MCP chrome-integration

- Revert Node version requirement from >=22 to >=20
- Revert code formatting changes (import statements and indentation)
- Archive obsolete chrome-extension implementation
- Clean up outdated documentation and scripts
- Reorganize MCP chrome-integration docs

* docs(mcp-chrome): 新增核心文档和更新 README

- 新增 02-features-and-architecture.md (27个工具完整参考)
- 新增 03-design-and-implementation.md (与 hangwin/mcp-chrome 对比)
- 新增 04-test-cases.md (35个测试用例)
- 更新 01-installation-guide.md (Extension ID 固定方案)
- 重写 README.md (对齐新文档结构)

* refactor(chrome-extension): 简化 sidepanel 并移除未使用代码

- 删除未使用的 Onboarding 组件
- 删除冗余样式文件 (App.css, timeline.css)
- 删除未使用的工具函数 (diffStats, diffUtils, sessionGrouping, tempFileManager, webviewUtils)
- 新增 MCP 工具状态横幅显示
- 隐藏不需要的 UI 按钮 (slash command, attach, edit mode)
- 移除未使用的变量 (clearToolCalls)
- 更新 manifest.json 和 native-messaging-host

* chore: regenerate lockfile for mcp-chrome-integration workspace

npm install reconciles the lock with the merged package.json: adds the
new packages/mcp-chrome-integration/app/* workspace subtree (371 deps).
No existing main dependencies were removed.

* chore(chrome-integration): drop dead logger + unused pino deps

native-server/src/util/logger.ts was 100% commented-out dead code (even
a hardcoded /Users/hang/... path) with zero importers; pino/pino-pretty
were declared but never imported (logging is console.error to stderr,
correct for a native-messaging host). Remove the file and both deps.

* feat(chrome-integration): WIP serve-backed agent client (Phase 1 backbone)

Replace the hand-rolled ACP client by driving `qwen serve` (main's
maintained HTTP daemon) through the SDK's DaemonClient. New
serve-agent/client.ts spawns `qwen serve --no-web` where the host used to
spawn `qwen --acp`, then uses DaemonClient (REST + SSE) for session
create / prompt / cancel / permission / streaming. Same public surface as
AcpClient so the native host can swap in place.

Compiles + typechecks against the real SDK API. NOT yet wired into
native-messaging-host.ts — wiring is gated on two items that need runtime
validation in a real Chrome + qwen env:
  - OAuth: host's onAuthenticateUpdate (in-extension authUri) maps to
    serve's server-side device-flow events — needs design.
  - permission requestId becomes string (was number) — host's
    permissionRequests map + handler need to follow.

acp/ kept in place until the serve path is validated. Packaging follow-up:
@qwen-code/sdk (→ core) as a dep of the standalone host is fine in the
monorepo but needs a publish-time story (bundle or resolve from the
co-installed qwen).

* feat(browser-ext): daemon-direct connection foundation (Phase 1, #5626)

First brick of the daemon-direct architecture from #5626: the extension
talks straight to a local `qwen serve` HTTP daemon instead of a native
messaging host.

- daemon/config.ts: resolve { baseUrl, token } — default loopback
  http://127.0.0.1:4170 (auth-free), overridable via chrome.storage.local.
- daemon/discovery.ts: GET /health probe so the side panel can show a
  "start qwen serve" hint instead of a broken chat when no daemon is up.

Both typecheck clean. (Pre-existing tsc errors live only in the orphaned
legacy sidepanel hooks — useWebViewMessages/useToolCalls etc. — which the
DaemonSessionProvider migration removes next.)

* docs(browser-ext): daemon-direct architecture spec (Phase 1+2, #5626)

Concrete implementation spec: Phase 1 (side panel as daemon client, no
daemon changes) and Phase 2 (browser tools as a client-hosted MCP server
over the daemon WS). Phase 2 reuses the existing SdkControlClientTransport /
SdkControlServerTransport pattern but moves the wire from the SDK subprocess
control plane onto qwen serve's WebSocket — a new public daemon-contract
surface, gated behind a capability flag (the open question in #5626).
Includes the daemon-lifecycle options for #5626 Q3.

* feat(browser-ext): Phase 1 — side panel as a daemon-direct client (#5626)

Side panel chat now talks straight to a local `qwen serve` HTTP daemon via
@qwen-code/webui's DaemonSessionProvider, replacing the native-messaging
relay (background/ + content/ untouched; nativeMessaging perm kept for now).

- SidePanelRoot.tsx: health gate — checkDaemonHealth(getDaemonConfig());
  shows a "run qwen serve" hint + Retry when unreachable, else mounts
  DaemonSessionProvider around App.
- App.tsx: rewritten daemon-driven — transcript/streaming/permissions/
  lifecycle from the webui daemon hooks (useTranscriptBlocks, useStreamingState,
  usePromptStatus, usePendingPermissions, useConnection, useActions); reuses
  the existing webui presentational components + ChromePlatformProvider.
- sidepanel/daemon/{transcriptItems,permission}.ts: adapters from daemon
  transcript/permission shapes to the existing UI components.
- Deleted the dead legacy native-messaging chat hooks/types.

Verified: sidepanel/daemon tsc clean; `npm run build` (esbuild) passes,
emits the side-panel bundle with the daemon wiring. (Pre-existing tsc errors
remain only in background/ + content/, out of scope until Phase 2.)

Daemon contract accepted live against the worktree `qwen serve`: /health,
/capabilities (advertises session_create/prompt/events/workspace_mcp),
POST /session runs end-to-end to the model-auth gate.

* feat(browser-ext): Phase 2 — browser tools over the daemon WS (#5626)

Reverse tool channel: a WS client (the extension) hosts an MCP server (its
browser tools) that the daemon's agent can call, carrying mcp_message
JSON-RPC frames over the daemon WS — reusing the SdkControlClientTransport /
SDK-MCP-server control-plane pattern. Gated behind capability flag
`client_mcp_over_ws` (opt-in; the public-contract piece flagged in #5626).

Daemon (core + cli/serve):
- core/tools/client-mcp-registrar.ts: ClientMcpRegistrar — id-correlation,
  pending/timeout, notifications fire-and-forget, exposes the
  sendSdkMcpMessage(server,msg) callback McpClientManager consumes.
- cli/serve/acp-http/client-mcp-ws.ts: ClientMcpWsConnection — handles
  mcp_register/mcp_message/mcp_unregister frames; pushes mcp_message down the
  WS; disposes on close. Hookup to the live agent McpClientManager is a
  ClientMcpServerProvider injection point (returns structured `not_wired`
  until the child↔parent reverse-IPC lands — see below).
- capability `client_mcp_over_ws` threaded through serve options/capabilities.

Extension (chrome-extension/background):
- browser-tools-server.ts: minimal hand-rolled MCP JSON-RPC
  (initialize/tools/list/tools/call) reusing the existing tool-catalog +
  router + executors (MVP 6 read-first tools); WS client to the daemon /acp
  with mcp_register + reconnect. Wired into the service worker behind a
  health probe; native messaging left intact.

Self-accepted (no LLM needed): 10/10 tests pass — a headless ws client
registers + answers the MCP handshake over mcp_message and the daemon
lists+CALLS the client-hosted chrome_read_page tool end-to-end over a real
socket. Builds: core + cli + extension esbuild all green; tsc clean.

NOT yet wired (out of scope here; needs acp-bridge + acpAgent reverse IPC):
the parent-process WS ↔ ACP-child McpClientManager hookup — the daemon's WS
lives in the parent serve process but sendSdkMcpMessage binds in the ACP
child. Single injection at the mountAcpHttp call site once that IPC exists.

* feat(serve): wire client-MCP-over-WS to the ACP child agent (#5626)

Closes the Phase 2 gap: a client-hosted (extension) MCP server's tool calls
now reach the agent's McpClientManager in the ACP CHILD, routed back to the
parent's ClientMcpRegistrar and out over the daemon WS. Opt-in via
QWEN_SERVE_CLIENT_MCP_OVER_WS=1 (the contract is still settling — dormant by
default; this is the public-daemon-contract piece flagged in #5626).

Contract additions:
- ACP ext-method `qwen/control/client_mcp/message` (child→parent, called UP):
  {server,payload} → {payload}; notifications resolve with a synthetic ack.
- runtime-MCP config flag `__clientMcpOverWs`: parent stamps it on the SDK-type
  add config; child KEEPS type:'sdk' (instead of stripping) so it binds an
  SdkControlClientTransport instead of the SDK subprocess control plane.
- BridgeOptions.clientMcpSender seam + ServeAppDeps.clientMcpSenderRegistry.

Round-trip: mcp_register(WS) → serve registers the connection's
ClientMcpRegistrar.sendSdkMcpMessage in a process ClientMcpSenderRegistry +
bridge.addRuntimeMcpServer(type:'sdk',__clientMcpOverWs) → child adds the
SDK server + runs initialize/tools/list, each frame child→parent via
client_mcp/message → BridgeClient looks up the sender → registrar pushes
mcp_message down the WS → extension answers → child discovers the tools.

Self-accepted LIVE (no LLM): integration-tests/cli/qwen-serve-client-mcp.test.ts
spawns a REAL qwen serve + REAL qwen --acp child under a mock OpenAI server,
a headless ws client registers + answers the MCP handshake, and the child
discovers the client-hosted chrome_read_page tool over the genuine
child→parent→WS channel (GET /workspace/mcp/chrome-tools/tools lists it).
Verified passing locally (24.6s). +5 bridge round-trip tests; 324 acp-bridge,
the 10 prior Phase-2, acpAgent 133, server+acp-http 675 all pass; builds clean.

Not exercised here: a real LLM turn driving tools/call (needs creds), and a
real Chrome extension as the WS client (headless ws stands in).

* fix(serve): session-scope runtime MCP servers so client tool calls resolve (#5626)

The reverse tool channel registered the client-hosted MCP server only on the
bootstrap/workspace Config, so discovery worked but a prompt — which runs
against an independent per-session Config from newSessionConfig→loadCliConfig
— couldn't resolve the tool ("not found in registry"), and the reverse WS
channel was never reached. Spec (docs/05) intends per-session scope.

Fix (minimal, additive, guarded; normal settings-based MCP servers unaffected):
- core/config.ts: add Config.getRuntimeMcpServers() (shallow copy of the
  private runtimeMcpServers map).
- acpAgent.ts newSessionConfig: copy the bootstrap Config's runtime MCP
  servers into a newly-created session Config before initialize() so its
  discovery binds that session's sendSdkMcpMessage (register-before-session).
- acpAgent.ts workspaceMcpRuntimeAdd/Remove: fan the add/remove out to every
  active session's McpClientManager (register-after-session), best-effort.

Test: the fake model now emits the fully-qualified registered name
mcp__chrome-tools__chrome_read_page (what a real model is handed); the
reverse channel still forwards the bare tool name to the client server.

Verified LIVE (no LLM, no creds): integration test now drives the FULL loop —
model→agent→session-registry resolution→reverse client_mcp/message over the
daemon WS→headless ws client returns CallToolResult→agent consumes it (tool
completed)→turn_complete. 2/2 integration tests pass (confirmed locally).
Regression: config 248, acpAgent 133, mcp-client-manager 101, client-mcp-ws 5
pass; builds clean. (server.test.ts has 2 pre-existing Web-Shell flakes,
identical on the unmodified baseline.)

Still needs a real Chrome extension (vs the headless ws stand-in) + a real
model turn for true browser behavior; the protocol round-trip is proven.

* chore(browser-ext): remove the dead Native Messaging stack (#5626)

Daemon-direct made Native Messaging obsolete — the extension talks to
`qwen serve` directly (chat over HTTP+SSE, browser tools over the daemon WS),
so the entire native-host stack is dead weight. Deletes ~15.5k lines:

- packages/mcp-chrome-integration/app/native-server/ — the whole native host
  (MCP servers, ACP client, Fastify server, doctor/register/report/postinstall,
  the superseded serve-agent backbone).
- extension background transport: native-messaging.ts, native-connection.ts,
  native-message-handler.ts, native-messaging-types.ts, ui-request-router.ts
  (+ its test). The still-used executor types (BrowserToolArgs / RawNetworkRequest
  / WebSocketSession / NetworkCaptureState) move to background/browser-tool-types.ts.
- service-worker rewired to daemon-direct only (drops the NativeMessaging
  init + the onMessage→routeUiRequest relay; keeps the browser-tools server start).
- esbuild.background.config.js drops the deleted native-messaging entry point.
- manifest.json drops the `nativeMessaging` permission.
- package.json scripts drop every native-server/native-host reference; dev-watch
  no longer spawns the native host.
- obsolete native-messaging docs (01-04) + scripts (diagnose/install/update) removed;
  README rewritten for daemon-direct.

Extension esbuild build green; tsc errors dropped (84 -> 69, all pre-existing
node:test/content typings). Daemon-side (cli/serve/core) untouched.

* chore(browser-ext): drop orphaned content-fetch-patch.ts (#5626)

* fix(serve): let browser extensions open the daemon WS reverse channel

The daemon-direct Chrome extension (#5626) connects to qwen serve's /acp
WebSocket to register its browser tools as a client-hosted MCP server.
Three gaps blocked the real-browser path. A node WS client in the
integration tests carries no browser Origin and completes the ACP
handshake, so none of these surfaced until a real Chrome connected:

- The WS CSRF check hard-coded loopback origins, so the extension's
  chrome-extension://<id> Origin was rejected with 403. Wire the
  existing --allow-origin allowlist into the WS upgrade check
  (acp-http/index.ts, server.ts) with the same match semantics as the
  REST allowOriginCors.
- parseAllowOriginPatterns rejected chrome-extension:// because its
  URL.origin is the opaque "null". Rebuild the canonical origin from
  scheme+host for opaque-origin schemes (auth.ts), with tests.
- The extension skipped the ACP initialize handshake and sent
  mcp_register directly, tripping the daemon's 30s initialize timeout.
  Send ACP initialize first and register only after the ack
  (browser-tools-server.ts).

Also allow console.* in the extension package (no stdio in the MV3
runtime) via the eslint no-console allowlist.

Verified end-to-end against a real Chrome: the daemon agent calls
mcp__chrome-tools__chrome_read_page and reads the live active tab.

* docs(browser-ext): Plan C (CDP tunnel) feasibility + implementation design

Assess routing chrome-devtools-mcp's ready-made DevTools toolset through the
extension's chrome.debugger to drive the user's real browser, instead of
re-implementing each tool in the extension (Plan A). Records, with source
verification against chrome-devtools-mcp@1.4.0 + puppeteer-core@25.2.0:

- the createCDPSession wall (why zero-change reuse fails — Target.attachToTarget
  is "Not allowed" for chrome.debugger; cdp-mcp throws in McpContext.from);
- the patch-package fork shape (pin 1.4.0 + a ~2-site patch, not vendor/submodule);
- the daemon /cdp browser-level CDP emulation + sessionId routing design;
- minimal browser-level command set, reusable prior art (playwright-mcp
  --extension), phased steps, risks, and the Plan A fallback.

Refs #5626.

* feat(serve): add CDP browser-level emulator for Plan C tunnel (#5626)

First component of the Plan C "CDP tunnel": a synthesis layer that fakes the
browser-level CDP topology so an external puppeteer client (chrome-devtools-mcp)
can connect over a future /cdp endpoint while page-domain commands are forwarded
to the one real tab via the extension's chrome.debugger.

Implements the exact contract a Phase 0 spike proved necessary: a tab->page
two-level target tree + recursive Target.setAutoAttach (browser attaches the tab
session; the tab session attaches the page session), with page-session commands
routed to forwardToTab and tab events re-tagged with the page session id. The
spike connected real puppeteer to a pure synthesis layer and ran
page.evaluate(() => 1 + 1) === 2. 7 unit tests cover the handshake + routing.

Refs #5626.

* feat(serve): add CDP tunnel reverse-link, /cdp glue, and bridge registry (#5626)

Plan C Phase 1 daemon core. The reverse-link forwards page-domain CDP
commands to the extension over cdp_command/cdp_result frames (id-correlated,
timeout) and re-tags cdp_event onto the page session; cdp-ws wires a per-
puppeteer-connection emulator to the reverse-link bound to the single active
extension bridge in a process-scoped registry. The emulator gains setTabInfo
so the synthetic targetInfo reflects the real tab after cdp_attach.

* feat(serve): wire /cdp upgrade branch and cdpTunnelOverWs flag (#5626)

Adds the /cdp WebSocket upgrade branch to acp-http (reusing the loopback /
host-allowlist / auth / CSRF checks) and routes inbound cdp_* frames on the
extension's /acp socket to the bound reverse-link. The extension connection
registers as the active CDP bridge eagerly at ACP initialize so a /cdp
puppeteer client can bind immediately (avoids the attach chicken-and-egg).
Feature flag cdpTunnelOverWs is wired exactly like clientMcpOverWs
(env QWEN_SERVE_CDP_TUNNEL_OVER_WS=1, capability cdp_tunnel_over_ws),
DEFAULT OFF — existing behaviour is unchanged when off.

* feat(browser-ext): add CDP bridge over the reverse /acp socket (#5626)

The extension answers cdp_attach by attaching chrome.debugger to the active
tab and cdp_command via chrome.debugger.sendCommand, replying cdp_result;
chrome.debugger.onEvent -> cdp_event, onDetach -> cdp_detach. Reuses the
existing browser-tools-server /acp socket (routes cdp_* frames; tears the
bridge down on socket close) and mutually excludes with the
chrome_network_debugger_* tools (one debugger per tab).

* build(deps): pin chrome-devtools-mcp 1.4.0 + puppeteer-core 25.2.0, patch McpContext (#5626)

Pins the two CDP-tunnel client deps (exact, to keep the version-specific
patch and puppeteer's hardcoded ExtensionTransport topology stable) and adds
patches/chrome-devtools-mcp+1.4.0.patch. The patch wraps McpContext.#init's
devtoolsUniverseManager.init / serviceWorkerConsoleCollector.init in try/catch
so the createCDPSession wall (Target.attachToTarget -> -32000 over
chrome.debugger) no longer crashes the server on startup; only performance_* /
service-worker console degrade. Applied via the existing postinstall
patch-package hook (same form as patches/ink+7.0.3.patch).

* test(serve): add Plan C /cdp end-to-end acceptance harness (#5626)

Node script that starts the real daemon with the flag on, connects a mock
extension over /acp (ACP initialize + mcp_register, answering cdp_command with
page-domain CDP), then puppeteer.connect to /cdp and asserts
page.evaluate(() => 1 + 1) === 2 through the real daemon + emulator +
reverse-link. Allowlists the harness dir in eslint's node-script globals.

* fix(serve): gate CDP page commands behind attach completion (#5626)

Real-Chrome testing surfaced an ordering race the mock acceptance missed: the
extension's chrome.debugger.attach is async (it pops the debugger banner), but
forwardToTab forwarded page-domain commands immediately, so a fast puppeteer
Network.enable reached the extension before attachedTabId was set and failed
"CDP tunnel not attached to a tab". The mock extension acked attach
synchronously, which hid the race.

Add an attach gate in CdpReverseLink: forwardToTab awaits the in-flight
cdp_attach to settle (success or failure) before forwarding. Verified
end-to-end against REAL Chrome — puppeteer read the live active tab (a GitHub
PR page) through the tunnel: pages=1, real url/title/body returned.

Refs #5626.

* refactor(chrome-extension): delete Plan A side panel, content scripts, and reverse tool channel

Tears out the superseded Plan A surface so the extension can become a pure
CDP-tunnel pipe (chat moves to the daemon web UI, browser tooling runs as
chrome-devtools-mcp over the /cdp tunnel):

- src/sidepanel/ (side-panel chat UI)
- src/content/ (content scripts; the CDP tunnel drives DOM/Input via
  chrome.debugger, no injection needed)
- background reverse tool channel: browser-tools-server, browser-network-tools,
  network-capture-utils, browser-tool-executors, tool-catalog, tool-router,
  mcp-tool-result, browser-tool-types, and their tests
- public/sidepanel/sidepanel.html static asset

Refs #5626

* refactor(chrome-extension): rewrite service worker as minimal daemon CDP client

The service worker is now the entire extension logic: probe the daemon /health,
open the /acp WebSocket, send the ACP initialize handshake (the daemon closes
the socket on a 30s init timeout otherwise and binds this connection as the CDP
bridge at that point), then route cdp_* frames into the CDP bridge with capped
backoff reconnect. No more reverse MCP tool server (chrome-tools is gone).

cdp-bridge: drop the browser-network-tools import and the network-capture
mutual-exclusion branch in handleAttach (the network tools are deleted, nothing
to exclude); remove the now-unused isCdpTunnelAttached export.

Refs #5626

* build(chrome-extension): trim manifest, build config, and deps to the CDP pipe

manifest: drop content_scripts, side_panel, and the sidePanel/webRequest/cookies/
scripting/webNavigation permissions; keep only debugger/tabs/activeTab/storage
plus background, key, host_permissions, icons, and action.

build: background esbuild now has a single service-worker entry point (content
script gone); delete the UI esbuild/postcss/tailwind configs and drop the UI
build step + build:ui scripts; sync-extension no longer special-cases the gone
sidepanel assets; dev-watch no longer spawns the UI watcher.

deps: remove the side-panel-only deps (@qwen-code/webui, react, react-dom,
markdown-it, and the @types + the postcss/tailwind/autoprefixer CSS toolchain).

Refs #5626

* chore(chrome-extension): drop dead externally_connectable hook (#5626)

* test(serve): add real-Chrome /cdp local verification script (#5626)

* test(serve): add cdp-mcp-over-tunnel layer-C smoke check

* fix(extension): keep the CDP tunnel alive with chrome.alarms

MV3 service workers idle out after ~30s, so the tunnel silently dropped
whenever no puppeteer client was driving it and the user had to keep the
Service Worker DevTools open to hold the worker awake. Register a 30s
chrome.alarms keepalive: the recurring onAlarm dispatch holds the idle
timer off, and each wake of a terminated worker re-runs the top level to
reconnect.

* feat(serve): auto-register chrome-devtools-mcp over the CDP tunnel

Plan C (#5626) last mile: when `qwen serve` runs with the CDP-tunnel flag,
the agent should be able to drive the user's real browser. Rather than
hand-writing browser tools, auto-register the (patched) chrome-devtools-mcp
as a session MCP server pointed at this daemon's /cdp endpoint, so its 29
ready-made DevTools tools flow through the tunnel.

- run-qwen-serve: forward QWEN_SERVE_CDP_TUNNEL_OVER_WS + _PORT into the
  spawned ACP child via childEnvOverrides (same path as the MCP budget env).
- acpAgent: buildCdpTunnelMcpServer() injects the server into the
  top-precedence sessionMcpServers tier when the flag + port are present and
  the package resolves; trust left unset so tools default to 'ask' (no silent
  auto-approval of browser control); best-effort skip otherwise.

No settings.json edit and no hand-written tools required.

* fix(serve): gate CDP bridge registration to the extension (#5626)

Auto-registering every /acp initialize as the CDP bridge was last-writer-
wins. Once an ACP agent connects over the same /acp endpoint (web UI, Zed),
it would capture the bridge and receive cdp_* frames it can't answer,
stealing the tunnel from the extension. Gate registration on
clientInfo.name === 'qwen-cdp-bridge'; the extension (and the acceptance
mock) now identify themselves that way, while agent clients are left alone.

* feat(extension): open the web UI when the toolbar icon is clicked

The extension has no UI of its own (pure CDP-tunnel pipe; chat lives in the
daemon web UI), so clicking the toolbar icon did nothing after the side panel
was removed. Wire action.onClicked to open the daemon baseUrl in a new tab so
the icon is a useful entry point instead of a dead click.

* feat: host the web UI in a Chrome side panel (#5626)

The extension is a pure CDP-tunnel pipe with no UI of its own, so after the
side panel chat was removed the toolbar icon did nothing. Bring the side panel
back as a thin host that iframes the daemon web UI (chat + tools), so the
sidebar is the everyday entry point and reuses the web UI's pages/components
instead of shipping a second UI in the extension.

- extension: side_panel + sidePanel permission; sidepanel.html/js frames the
  daemon baseUrl; toolbar icon opens the panel (openPanelOnActionClick).
- daemon: the Web Shell sent frame-ancestors 'none' + X-Frame-Options: DENY,
  which blocked the iframe. Allow framing only for chrome-extension origins
  explicitly passed via --allow-origin; everything else still gets DENY.

* fix: prefer chrome-devtools over computer-use under the CDP tunnel + keep MV3 worker alive during attach (#5626)

Two issues surfaced driving the real agent:

1. The agent picked the OS-level computer-use tool (cua-driver) for browser
   tasks instead of the injected chrome-devtools-mcp — heavyweight screenshot/
   click loop that pegged a CPU and stalled turns. Disable computerUse when the
   CDP tunnel flag is on so browser automation goes through the tunnel.

2. The extension's MV3 service worker idled out *between* CDP commands (the
   agent pauses to think), detaching chrome.debugger and hanging the next
   command. Add a sub-30s keepalive while attached; the 30s alarm only covered
   idle reconnects, not in-flight attachments.

* chore(extension): stop tracking .extension-key.pem (signing private key)

The extension signing private key was committed and pushed — anyone with repo
access could impersonate the extension. Stop tracking it and gitignore *.pem.
Load-unpacked debugging needs only the public "key" in manifest.json, so this
doesn't affect dev on any machine; the .pem is kept locally for packaging.

* refactor(chrome-extension): flatten to packages/chrome-extension

Drop the mcp-chrome-integration wrapper + dead native-server (daemon-direct
no longer uses native messaging). The extension is now a top-level workspace
at packages/chrome-extension. Updated root workspaces, eslint globs, doc-path
comments, and the two node scripts' global directives. cli + extension builds
and eslint verified green.

* test(serve): assert cdp_tunnel_over_ws in the capability registry

The cdp_tunnel_over_ws capability was added to SERVE_CAPABILITY_REGISTRY +
CONDITIONAL_SERVE_FEATURES but the test's EXPECTED_REGISTERED_FEATURES and the
conditional drift-insurance branch weren't updated, failing 3 registry tests.
Add it to the expected list (after client_mcp_over_ws, matching registry order)
and add its assertion branch (predicate accepts/rejects the cdpTunnelOverWsEnabled
toggle).

* fix(serve): address review comments on CDP tunnel + client-MCP wiring (#5777)

- guard post-async ws.send() with readyState OPEN (daemon crash on extension
  disconnect, sendClientMcpAck + cdp endpoint sends)
- make client-mcp-sender-registry delete() ownership-aware (cross-connection
  server-name collision)
- type the __clientMcpOverWs runtime config flag carrier
- document the CDP tunnel trust model (loopback + bridge-gated dumb pipe)

* chore(cdp-tunnel): set copyright year to 2026 on files created this year

The Plan C / #5626 files were authored in 2026 but carried a 2025 header.
service-worker.ts (created last year under #1432) keeps 2025.

* feat(chrome-ext): add side-panel onboarding gate + fix packaging

The side panel framed the daemon Web Shell unconditionally and showed a
static "Connecting…" line. When no daemon was reachable, or the daemon
wasn't started with --allow-origin (so frame-ancestors blocks the iframe),
the user was stuck on "Connecting…" with no guidance, and discovery.ts's
health check was never wired in.

Wire a health/capabilities gate into the panel:
- probe GET /health, then GET /capabilities
- down                         → "Start qwen serve" + the exact command
- up but no `allow_origin` feat → "Allow this extension" + the command
- ready                        → frame the Web Shell

The command is built from chrome.runtime.id at runtime, so it always names
this extension's real origin (dev-unpacked or published) — no need to know
or hardcode the id, and no publish-first chicken-and-egg. The pure decision
helpers live in onboarding-logic.js.

Also:
- fix `npm run package` so manifest.json sits at the zip root (the old
  `zip ... extension/` nested it under extension/, which the Chrome Web
  Store rejects)
- gitignore that packaging zip; add a package README

Part of #5626. PR #5777.

* docs(cdp-tunnel): trim over-long Plan C comments (ponytail)

Comment-only: compress multi-paragraph design rationale to intent, keep the
non-obvious why (trust model, bridge gate, attach ordering) + ponytail
ceilings. ~-91 lines, no code touched, build + tests green.

* fix(serve): address second-round review comments on the CDP tunnel (#5777)

- close the bound /cdp puppeteer socket on extension disconnect (was hanging
  ~170s on CDP timeout) via an onExtensionGone hook
- reject a 2nd concurrent /cdp client instead of silently clobbering routeInbound
- narrow extension host_permissions from <all_urls> to localhost (chrome.debugger
  needs no host perm; only the /health fetch needs localhost)
- log safeWsSend drops under serve debug mode so a dead tunnel is diagnosable

* feat(chrome-ext): polish side-panel welcome into a terminal console

Replace the bare welcome with a console that matches the product (a CLI
daemon): the command is the hero, typed at a `$` prompt with a blinking
cursor inside a titled terminal card. Warm-charcoal / electric-lime,
light + dark aware (prefers-color-scheme), staggered load-in, a pulsing
"listening" status, and a reduced-motion guard. No web fonts / no inline
JS (the extension CSP allows neither).

No behavior change: same /health + /capabilities gate and the
chrome.runtime.id-derived command. Mechanics tidied alongside the markup:
visibility toggles a .hidden class (CSS owns the flex layout), the copy
button updates a label span, and the command is prefilled synchronously
so first paint isn't an empty prompt.

PR #5777.

* feat(chrome-ext): click-to-copy command + centered copy button

Make the onboarding command easier to grab: the whole command row is now
click-to-copy (keyboard-reachable, Enter/Space), and a centered "Copy
command" button sits at the foot of the terminal card. Both flash a
check-mark "Copied" confirmation; the small top-bar button is gone.

No gate-logic change. PR #5777.

* test(serve): cover CDP-tunnel + client-MCP regression guards

Add the four focused unit tests flagged in review for load-bearing
reverse-channel paths that had no coverage:

- ClientMcpSenderRegistry: ownership-scoped delete — a disconnecting
  connection must not remove an entry a peer re-registered under the
  same name.
- CdpTunnelRegistry: register/supersede/unregister lifecycle, inbound
  routing delegation, onExtensionGone-on-disconnect, and the
  stale-unregister guard that must not evict a newer active bridge.
- CdpReverseLink: a forwarded command rejects when its per-command
  timer expires (not just on bulk dispose).
- safeWsSend: drops (no send, no throw) on a CLOSED/CLOSING socket.

safeWsSend is extracted from acp-http/index.ts into its own module so
it's unit-testable in isolation; behavior unchanged.

PR #5777.

* fix(serve): address bot code-review findings on the CDP tunnel (#5777)

- cdp-bridge: tear down listeners before re-attach (was double-registering →
  duplicate cdp_event frames corrupting puppeteer)
- emulator: return a CDP error for an unknown session instead of fake success
- registry: notify the superseded bridge so the old /cdp closes (single-puppeteer)
- deps: move chrome-devtools-mcp + puppeteer-core to optionalDependencies (~26MB)
- tests: entry-script validation, deliverClientMcpMessage error branches,
  registry supersede

* fix(chrome-ext): revert side panel to welcome when the daemon stops

Once the panel framed the Web Shell it stopped probing, so if the daemon
later went away the iframe was left showing Chrome's localhost
connection-refused page with no way back. Keep probing after framing and,
after a short tolerance (2 misses, ~5s, so a transient blip doesn't nuke a
live chat), clear the iframe src and show the welcome screen again.

PR #5777.

* fix(chrome-ext,serve): address review comments on the CDP tunnel

- service-worker: redact the bearer token from the connect log, and guard
  connect() against a still-CONNECTING socket so a rapid reconnect can't
  orphan an in-flight handshake.
- acpAgent: don't clobber a user-configured `chrome-devtools` MCP server
  with the tunnel auto-wire.
- cdp-reverse-link: log dropped/unexpected inbound frames via an optional
  diagnostic sink instead of swallowing them silently.
- name the cross-package `qwen-cdp-bridge` client-name constant on both
  sides instead of repeating the bare string.

PR #5777.

* refactor(chrome-ext): inline onboarding helpers, drop dead pollTimer

onboarding-logic.js was a 65-line file (mostly JSDoc) for three trivial
helpers and a constant, split out "for testability" that was never used.
Fold them into sidepanel.js (decideState collapses into probeState's
return; resolveBaseUrl/allowOriginCommand become a one-liner each) and
delete the file + its import.

Also drop `pollTimer`: the welcome-fallback change made it write-only (the
only clearInterval was removed), which trips no-unused-vars.

PR #5777.

* fix(serve,chrome-ext): more CDP-tunnel review fixes

- sidepanel: pass the bearer token through the Web Shell URL fragment so a
  token-gated daemon doesn't 401 every framed request.
- server: throw if deps.bridge is injected without deps.clientMcpSenderRegistry
  (the bridge is already wired to its own sender; a fresh one would be orphaned).
- cdp-browser-emulator: surface unhandled browser-level CDP commands via an
  optional log sink (keep the empty-result ack, with a TODO).
- cdp-bridge: only treat "already attached" as ours when attachedTabId === tabId
  (a foreign DevTools owner now errors), and detach the previous tab on switch
  so Chrome drops its debug banner.
- build.js: add packages/chrome-extension to the build order so root build
  exercises the extension bundle.

PR #5777.

* fix(serve,chrome-ext): harden CDP-tunnel + client-MCP reverse channel

- client-mcp-ws: cap registered servers per connection (max 10) and re-check
  `disposed` after the provider round-trip so a WS close mid-register can't
  leave a zombie server; add registrar serverCount().
- acp-http: rate-limit client-MCP frames (mcp_register/unregister at the
  mutation tier, mcp_message at read) and cap concurrent fire-and-forget
  register/unregister dispatch (max 8) to stop DoS amplification.
- cdp tunnel: on /cdp puppeteer disconnect send a `cdp_release` frame so the
  extension detaches chrome.debugger instead of leaving the tab's debug banner
  up until /acp dies.
- acpAgent: skip chrome-devtools auto-registration (with a diagnostic) when the
  /cdp tunnel requires bearer auth — the ACP child can't authenticate to it.

PR #5777.

* fix(chrome-ext,serve): address second-round CDP-tunnel review

- service-worker: only the *active* socket's close tears down the bridge — a
  stale daemon-forced close must not detach the new connection's debugger.
- daemon config + sidepanel: fail closed on a non-loopback baseUrl so a tampered
  chrome.storage value can't exfiltrate the bearer token off-host (background
  fetch/WS bypass host_permissions).
- sidepanel: reentrancy guard on tick() so overlapping slow probes don't burn
  the framed-miss tolerance and flash the welcome screen mid-chat.
- cdp-bridge: reentrancy guard on handleAttach so overlapping cdp_attach frames
  can't interleave teardown and corrupt attachedTabId.
- add cdp-ws.test.ts: regression cover for no-bridge reject, second-client
  reject, onExtensionGone fail-fast, cdp_release on dispose, and the
  superseded-bridge cleanup guard.

PR #5777.

* fix(serve,webui): address third-round CDP-tunnel review

- safe-ws-send: wrap the debug-mode writeStderrLine in try/catch so a broken
  stderr (EPIPE on a piped/closed log) can't break the "never throw on a dead
  socket" contract production callers rely on.
- ChromeToolCall: index-access rawInput['name'] to satisfy
  noPropertyAccessFromIndexSignature.

PR #5777.

* fix(serve,chrome-ext): address #5777 review round 4

- acp-http: send mcp_error back on an unexpected client-MCP handler rejection
  (register/unregister callers otherwise hang); log when the inflight cap rejects.
- cdp-ws: log the happy-path cdp_release dispatch for oncall tracing.
- run-qwen-serve/acpAgent: don't pass a bogus "0" CDP-tunnel port for ephemeral
  --port 0, and emit a stderr diagnostic when the tunnel is disabled for a
  missing/invalid port instead of failing silently.
- cdp-bridge: handle a cdp_release that races an in-flight handleAttach so a late
  attach can't leave a debugger attachment with no live /cdp client.
- service-worker: close the WS on an ACP initialize error so the daemon doesn't
  keep holding a non-functional CDP bridge.

* fix(serve,chrome-ext): address pr-review findings (#5777)

- client-mcp-sender-registry: gate the child-side runtime-server teardown on
  ownership too (Config.removeRuntimeMcpServer is not owner-scoped), so a
  disconnecting connection can't kill a server a later connection re-registered
  under the same name. (P2)
- service-worker: carry the bearer token via the `qwen-bearer.*` WS subprotocol
  (matching the web-shell + daemon decoder) instead of a `?token=` query the
  daemon never reads, so a token-gated daemon no longer 401-reconnect-loops. (P3)
- run-qwen-serve: advertise client_mcp_over_ws / cdp_tunnel_over_ws in the
  bootstrap /capabilities too, matching the runtime path. (P3)

* chore(webui): remove unused ChromeToolCall component (#5777)

ChromeToolCall was added in a debugging commit but is never wired into the
tool-call routing (getToolCallComponent) — there's no `chrome` tool kind and
the only references were barrel re-exports. Dead code; remove it.

* fix(serve,chrome-ext): address #5777 review round 5 (diagnostics)

- service-worker: log the WS close code/reason so failure modes aren't
  indistinguishable (e.g. the daemon's 1011 "no extension connected").
- acpAgent: containment-check the resolved chrome-devtools-mcp bin path so a
  malformed `bin` field can't escape the package dir.
- cdp-tunnel-registry: log when a new extension bridge supersedes a stale one.

* fix(chrome-ext,serve): address #5777 review round 6

- cdp-bridge: ack the attach (as an error) before tearing down on a
  release-during-attach, so the daemon's reverse link doesn't hang ~170s
  waiting for a cdp_attached that never arrives.
- acp-http: rename isFireAndForget -> dispatchOffQueue + clarify the comment;
  register/unregister are dispatched off-queue but still expect a response ack,
  so the name no longer reads as "no response."

* fix(serve,cli): address #5777 review round 7

- config: warn (stderr) when QWEN_SERVE_CDP_TUNNEL_OVER_WS overrides an explicit
  tools.computerUse.enabled=true, so the effective config isn't a silent surprise.
- client-mcp-ws: document the intentional idempotency of handleUnregister.
- cdp-reverse-link: add tests for the attach gate — forwardToTab parks behind an
  in-flight attach, the cdp_attach timer rejects, and the gate opens on timeout
  so commands don't hang.

* fix(serve,core): address #5777 review round 8

- mcp-client: only treat the first stdio arg as a local entry script when
  it is clearly a filesystem path. A bare includes('/') also matched scoped
  npm package names (npx @scope/pkg), wrongly resolving them under the
  workspace and throwing before the runner ran. Adds a regression test.
- client-mcp-sender-registry: reject shadowedSettings so a browser-hosted
  WS client cannot shadow a user-configured MCP server name; roll back the
  child-side add.
- cdp-ws: close the puppeteer socket when /cdp attach fails so dispose()
  clears cdpBound/routeInbound, instead of a stuck tunnel until restart.

* fix(serve,chrome-ext): address #5777 review follow-ups

* fix(serve): satisfy CDP inbound frame guard typing

* fix(serve): address CDP tunnel review feedback

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.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-06-28 15:57:31 +00:00
qwen-code-ci-bot
8b65a555a0
chore(release): v0.19.3 (#5952)
* chore(release): v0.19.3

* docs(changelog): sync for v0.19.3

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-28 06:59:17 +00:00
carffuca
cf03af5d31
feat(web-shell): stream-highlight code blocks and fix fence-language aliases (#5869)
* feat(web-shell): stream-highlight code blocks and fix fence-language aliases

- Highlight assistant code fences live as they stream in via @shikijs/stream,
  replacing the per-token whole-block re-tokenize that flickered between plain
  grey and colored and only settled at the end of the turn.
- Fix fence-language aliases (`ts`/`js`/`py`/... and uppercase like `SQL`) that
  previously rendered unhighlighted because the supported-language gate only
  matched canonical Shiki ids.
- Consolidate the static and streaming paths onto a single lazily-created
  JavaScript-regex-engine highlighter (no WASM, on-demand language loading) and
  hand off without a grey flash; bump shiki ^1 -> ^4 and add @shikijs/stream.

* fix(web-shell): address review feedback on streaming code highlight

- Stabilize the `code` renderer via an IsStreamingContext so the
  CodeBlock/StreamingCodeBlock instances are reused (not remounted) across the
  streaming→settled transition, keeping the no-flash hold effective; drop the
  now-redundant COMPONENTS_STREAMING split.
- Log a warning when the streaming highlighter fails to load, instead of
  failing silently to plain text.
- Export `enqueueSuffix` and add unit tests for its no-op/extend/diverge/throw
  branches, plus a streaming→settled hand-off render test.

* fix(web-shell): harden code highlighter (review round 2)

- Guard highlightToHtmlSync with try/catch so a synchronous tokenization error
  falls back to the async path instead of crashing the render tree.
- Dedupe concurrent loadLanguage calls via a pending-languages map (two callers
  could previously both pass the `has` check and double-load).
- Add unit tests for codeHighlighter: highlightToHtml output, the sync
  cold→warm transition, the 30K-char sync bail-out, and concurrent-load dedup.

* fix(web-shell): guard oversized streaming fences + review cleanups

- Fall back to the plain renderer while streaming once the block or its trailing
  line exceeds a threshold (`exceedsStreamingLimit`), so a large single-line /
  minified fence can't pin the UI via @shikijs/stream re-tokenizing the unstable
  current line on every chunk.
- Drop `shell`/`zsh` from SUPPORTED_LANGUAGES — they were dead entries shadowed
  by the alias map (→ bash).
- Tests: add `__resetForTesting` to drop cross-test ordering coupling; add
  highlighter retry / pending-language cleanup tests, `exceedsStreamingLimit`
  unit tests, an oversized-fence fallback test, and a highlighter-load-failure
  fallback test for StreamingCodeBlock.

* refactor(web-shell): stream-highlight code blocks via synchronous per-chunk re-render (drop @shikijs/stream)

---------

Co-authored-by: 易良 <1204183885@qq.com>
2026-06-27 03:26:18 +00:00
Heyang Wang
1344f34147
feat(mcp): reconcile MCP servers live on settings change (#5561)
* feat(mcp): reconcile MCP servers live on settings change

Hot-reload MCP servers when settings.json changes (issue #3696 sub-task 3):
editing mcpServers / mcp.allowed / mcp.excluded now connects, disconnects, or
restarts only the affected servers in place, without restarting the session or
losing conversation context.

- Part A: Config runtime setters + reinitializeMcpServers incremental
  reconcile; align the shared-pool path with the #4615 pending-approval gate
- Part B: SettingsWatcher subscriber (hotReload.ts), gated on a mcpServers +
  gating-list diff; flip the three MCP schema keys to hot-reloadable
- Part D: re-fire the approval modal for a gated server left pending by an edit
- Part E: /mcp shows why a gated server was skipped (pending / rejected)
- Record connection fingerprints on the bulk and lazy-connect paths so an edit
  to a server first connected via those paths is not silently dropped
- Design doc (en/zh) incl. the admission-stance boundary clarification

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

# Conflicts:
#	packages/cli/src/config/settingsSchema.test.ts

# Conflicts:
#	packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx

# Conflicts:
#	packages/cli/src/gemini.tsx

* feat(mcp): reconcile MCP servers live on settings change

Hot-reload MCP servers when settings.json changes (issue #3696 sub-task 3):
editing mcpServers / mcp.allowed / mcp.excluded now connects, disconnects, or
restarts only the affected servers in place, without restarting the session or
losing conversation context.

- Part A: Config runtime setters + reinitializeMcpServers incremental
  reconcile; align the shared-pool path with the #4615 pending-approval gate
- Part B: SettingsWatcher subscriber (hotReload.ts), gated on a mcpServers +
  gating-list diff; flip the three MCP schema keys to hot-reloadable
- Part D: re-fire the approval modal for a gated server left pending by an edit
- Part E: /mcp shows why a gated server was skipped (pending / rejected)
- Record connection fingerprints on the bulk and lazy-connect paths so an edit
  to a server first connected via those paths is not silently dropped
- Design doc (en/zh) incl. the admission-stance boundary clarification

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

* fix(mcp): harden hot-reload teardown and reconcile (review follow-ups)

Address reviewer findings on the MCP hot-reload changes:

- Extract purgeServerRegistries() and use it at every teardown path, fixing
  the discovery-timeout handler which leaked prompts/resources (only tools
  were purged) for a server that stalled tools/list past the timeout.
- Surface reconcile failures via AppEvent.LogError so a failed settings edit
  is visible to the user, not just under --debug.
- Make a single-session config edit to a discovery filter (trust /
  includeTools / excludeTools) reconnect the server so discover() re-applies
  it: connectionIdOf stays transport-only; add singleSessionConnectedKeyOf and
  rename connectionFingerprints -> connectedConfigKeys.
- Make a coalesced reinitializeMcpServers await the in-flight pass + its drain
  (store mcpReconcilePromise) so the caller no longer emits approval events /
  logs "complete" before its change is applied; coalesced callers share the
  failure.
- Assert removeResourcesByServer in the fingerprint-change tests.

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

* feat(mcp): bound hot-reload MCP admission and explain why servers are unavailable (#3696) (#5561)

- K: treat the startup --allowed-mcp-server-names flag as an immutable upper
  bound — a runtime settings edit may narrow MCP admission within it but never
  widen beyond it; with no flag, settings fully drive admission.
- H: preserve an explicit `mcp.allowed: []` as deny-all (don't collapse to
  undefined / allow-all), matching boot semantics, and make mcpGatingEqual
  distinguish absent (allow-all) from [] (deny-all) so the change reconciles.
- B: classify why an MCP server is unavailable (removed / not_allowed /
  excluded / pending_approval) and route the tool-not-found message to the
  right recovery action; track removals against the gating-independent merged
  map (dropping the prev-effective snapshot param).

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

* docs(mcp): document hot-reload admission bound, deny-all, and unavailable reasons (Part F)

Reflect the K/H/B changes in the sub-task 3 design doc: add Part F (CLI
--allowed-mcp-server-names as an immutable upper bound, mcp.allowed: [] as
deny-all, and getMcpServerUnavailableReason routing the tool-not-found message),
and fix the now-superseded "settings can widen beyond the startup allowlist"
admission-stance note and verification item 11.

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

* test(serve): pre-approve gated MCP servers in daemon baseline harness

The pool/daemon discovery path now honors #4615 pending-approval gating, so the workspace-scoped MCP servers the amplification suite declares in .qwen/settings.json are skipped as pending and never spawn (the suite timed out waiting for grandchildren). Add approveWorkspaceMcpServers() to the harness (keyed by the realpath workspace to match the daemon's canonicalized --workspace) and pre-approve the fixtures before boot, mirroring simple-mcp-server.test.ts.

---------

Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 14:50:37 +00:00
qwen-code-ci-bot
5bb79bc67c
chore(release): v0.19.2 (#5830)
* chore(release): v0.19.2

* docs(changelog): sync for v0.19.2

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-24 15:00:58 +00:00
qwen-code-ci-bot
8eb5770812
chore(release): v0.19.1 [skip ci]
* chore(release): v0.19.1

* docs(changelog): sync for v0.19.1

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-23 17:26:10 +08:00
qwen-code-ci-bot
57156522bd
chore(release): v0.19.0 [skip ci]
* chore(release): v0.19.0

* docs(changelog): sync for v0.19.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-23 11:38:15 +08:00
qqqys
b9c5e3566b
feat(voice): voice dictation with native capture, streaming, and biasing (#5502)
* feat(voice): voice dictation with native capture, streaming, and biasing

Add voice dictation for the prompt input:
- /voice [hold|tap|off|status] command + general.voice.{enabled,mode,language,protocol} settings; push-to-talk via Space, /model --voice to pick the model
- Native microphone capture (@qwen-code/audio-capture, miniaudio N-API) with arecord/SoX fallback, silence auto-stop, cold-start warm-up, and macOS permission query
- Batch transcription via DashScope Qwen-ASR (OpenAI-compatible chat/completions + input_audio) with language + keyterm biasing and an echo guard
- Live streaming over the DashScope realtime WebSocket (fun-asr-realtime / paraformer-realtime-v2) with interim text and an input-level waveform, behind voice.protocol=dashscope-realtime
- Rich VoiceIndicator UI (state, level meter, live partial transcript)
- Cross-platform prebuilds via prebuildify + node-gyp-build and a CI matrix

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

* feat(cli): route voice ASR by model

* feat(cli): polish voice realtime parity

* ci: fix voice workflow checks

* fix(cli): address voice review blockers

* fix(cli): harden voice transcription failures

* fix(cli): address voice PR review blockers

* fix(cli): harden voice review follow-ups

* fix(voice): handle realtime review blockers

* fix(cli): add voice command i18n keys

* fix(cli): address voice review blockers

* fix(cli): address voice review follow-ups

* fix(voice): harden review edge cases

* fix(voice): address release and realtime review blockers

* fix(voice): address realtime suggestion followups

* fix(voice): handle realtime review followups

* fix(voice): address realtime review blockers

* fix(voice): address review feedback

* fix(voice): address recorder review feedback

* docs(voice): document ssrf guard boundary

* fix(voice): address review feedback

* fix(voice): preserve warm recorder session safety

* fix(voice): address stream review suggestions

* fix(voice): address multi-round review findings

Realtime/streaming:
- Salvage an already-committed transcript when the WebSocket closes right
  after finish() instead of rejecting the whole dictation
  (qwenAsrRealtimeSession, voiceStreamSession) + regression tests.

useVoiceInput state machine:
- Single-shot finalize guard so a tap-stop racing the silence auto-stop can't
  double-stop the recorder and surface a spurious failure.
- Reset mountedRef on (re)mount so StrictMode (DEBUG) can't freeze the voice UI.
- Widen the hold-mode first-press release window above common key-repeat delays.

Model selection:
- Reject ids with no ASR transport at /model --voice and in the model dialog via
  a new isSelectableVoiceModel; move resolveVoiceTransport into voiceModel so the
  record-time config resolver stays transport-agnostic (+ tests).

macOS mic permission:
- Surface the not-determined state in voice warmup so the first dictation isn't
  silently lost behind the TCC dialog.

Native packaging:
- Make the audio-capture native install non-fatal (falls back to SoX/arecord) so
  a voice-only build failure can't break npm ci.
- Download audio-capture prebuilds before building standalone release archives.

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

* fix(voice): address review blockers

* fix(voice): update batch recording audio level

* fix(voice): tap-mode transcript loss, stream leaks, and dead keyterm cleanup

- Tap-mode dictation submitted a stale empty buffer.text, wiping the just-
  inserted transcript and sending nothing: thread the resulting prompt text
  through onSubmit(text) instead of reading buffer.text back synchronously
  after the async insert (useVoiceInput, InputPrompt).
- Streaming finalize leaked the WebSocket session when recorder.drain() threw:
  abort the session before propagating the error.
- voiceStreamSession: reject the connect promise when 'task-finished' arrives
  before 'task-started' instead of hanging forever in 'transcribing'.
- Remove dead keyterm enrichment (project/branch/recent-file paths were
  unreachable after the privacy fix) and the now-inert CJK echo guard, plus the
  tests that asserted that removed behavior; fix the misleading "OpenAI prompt
  field" comment.
- Add the missing 'Voice Model' and macOS mic-permission i18n keys to
  en/zh/zh-TW (they were falling back to English; check-i18n doesn't flag keys
  absent from en).

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

* fix(voice): reject partial stream transcripts

* fix(cli): let dialogs consume voice keys first

* fix(voice): reject incomplete qwen realtime transcripts

* fix(voice): handle stream review blockers

* fix(voice): salvage qwen realtime transcript on close

* fix(voice): sanitize streamed transcript text

* test(audio): run audio capture tests in CI

* fix(voice): address review blockers

* fix(voice): report stream close while recording

* fix(voice): reduce keyterm echo false positives

* fix(voice): handle realtime close diagnostics

* fix(voice): address realtime review blockers

* fix(lint): allow legacy voice filenames

* chore(cli): rename voice files to kebab-case

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-22 14:33:36 +08:00
Zqc
3d6b6f7475
feat(lint): enforce kebab-case filenames with ESLint (#4797)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
Co-authored-by: 俊良 <zzj542558@alibaba-inc.com>
2026-06-22 10:10:07 +08:00