Commit graph

1347 commits

Author SHA1 Message Date
jinye
d96de59acf
feat(serve): bound daemon ACP NDJSON buffers (#8911)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 14:36:49 +00:00
jinye
542ef73fd3
chore(serve): Log session continuation admissions (#8932)
* chore(serve): Log session continuation admissions

Refs #8923

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

* test(serve): Tighten continuation log assertions

Refs #8923

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 14:34:08 +00:00
jinye
74cfbccf49
fix(web-shell): Enforce prompt-safe session navigation (#8931)
* fix(web-shell): Enforce prompt-safe session navigation

Refs #8923

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

* test(webui): Harden session navigation cleanup

Refs #8923

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

* fix(web-shell): Preserve admission state across navigation

Refs #8923

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 14:33:35 +00:00
易良
89708569f7
fix: add structured error code to SessionNotFoundError for session-closing retry (#8884)
* fix: add structured error code to SessionNotFoundError responses

PR #8864 retried session switches while the target session is closing,
but relied on fragile string matching against the daemon's error message.
This commit:

1. Adds a `code` property to `SessionNotFoundError` — automatically set
   to `'session_closing'` when the extra message mentions "closing",
   otherwise `'session_not_found'`.

2. Includes `code` in the HTTP JSON response body so clients can
   distinguish closing (transient) from genuinely missing sessions
   without depending on error message text.

3. Updates the WebUI retry check in `DaemonSessionProvider` to use
   `errorBody.code === 'session_closing'` instead of matching
   `endsWith('The session is closing; retry after close completes')`.

4. Fixes an inconsistent error message in `rewindSession` that used the
   short `'The session is closing'` without the retry suffix.

Closes: #8864 (follow-up)

* fix(daemon): expose session closing code

* docs(serve): document session closing codes

* fix(acp): preserve closing code after restore waits

* fix: restore class pin in bridge test and update error taxonomy

- Add toBeInstanceOf(SessionNotFoundError) alongside toMatchObject
  to preserve the envelope type assertion
- Document session_closing code in 18-error-taxonomy.md

* chore: drop unrelated merge formatting
2026-08-11 13:01:25 +00:00
Nothing Chan
e6a3272271
feat(cli): expose reasoning effort through ACP (#8526)
* feat(cli): expose reasoning effort to ACP clients

* fix(cli): address ACP reasoning effort review

* fix(cli): address ACP reasoning effort review round 2

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

* fix(cli): migrate /effort dialog to applyReasoningEffort helper

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

* fix(acp): harden set_config_option routing and rejection messages (#8526)

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 10:32:33 +00:00
jinye
00248b1a53
fix(webui): Make cross-session switching transactional (#8882)
* fix(webui): make cross-session restores transactional

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

* fix(web-shell): preserve sessions during navigation

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

* codex: address PR review feedback (#8882)

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

* fix(webui): address transactional switch review feedback

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

* fix(web-shell): preserve catalog updates across session switches

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 09:38:36 +00:00
jinye
63fbf1bd14
perf(cli): Cache persisted session catalogs (#8892)
* perf(cli): Cache persisted session catalogs

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

* fix(cli): Pin LiveTask rollback storage runtime

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

* test(cli): Strengthen session catalog cache coverage

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

* test(cli): Pin LiveTask bridge identity

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 09:34:49 +00:00
jinye
096bb0e3b9
feat(web-shell): Share session catalog scheduling (#8891)
* feat(web-shell): Share session catalog scheduling

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

* fix(web-shell): Harden session catalog reconciliation

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

* fix(web-shell): Restore session catalog CI coverage

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 09:13:14 +00:00
ytahdn
1a2c5026b2
fix(web-shell): reconcile mid-turn messages with daemon state (#8798)
* fix(web-shell): reconcile mid-turn messages with daemon state

* test(serve): update mid-turn capability expectation

* test(mid-turn): cover reconciliation mutants and restore serve protocol docs (#8798)

* fix(serve): close mid-turn promotion admission and delivery gaps (#8798)

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

* fix(serve): keep anonymous mid-turn enqueues off the shared queue surface (#8798)

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

* fix(sdk): account for mid-turn APIs in bundle budget

* test(acp-bridge): use vi.waitFor for async prompt-drain assertions (#8798)

* fix(serve): reconcile mid-turn steering safely

* fix: reconcile mid-turn messages safely

---------

Co-authored-by: ytahdn <ytahdn@users.noreply.github.com>
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-11 07:24:56 +00:00
易良
70672f8fb8
fix(cli): avoid duplicate context usage in footer and status line (#8749)
* fix(cli): avoid duplicate context usage in footer and status line

The built-in default status line preset includes `context-used`, and the
footer renders its own context indicator unless `hideContextIndicator` is
set, so context usage was shown twice out of the box.

Treat `ui.statusLine.hideContextIndicator` as tri-state: an explicit
boolean still wins in both directions, and when it is unset a preset
status line containing `context-used` or `context-remaining` hides the
footer indicator. Command status lines are unchanged — their output is
opaque, so it is never inspected for context information.

Fixes #8695

* fix(cli): preserve status line context override

* fix(cli): preserve status line context semantics

* fix(cli): keep context visible in narrow footers

* fix(cli): keep context visible when status line clips

* fix(cli): preserve context indicator visibility

* fix(cli): match status line wrap layout
2026-08-11 07:18:48 +00:00
jinye
60c18256b6
feat(cli): clean up OpenAI logs in non-interactive sessions (#8893)
* feat(cli): clean up OpenAI logs in non-interactive sessions

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

* codex: address PR review feedback (#8893)

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

* codex: address PR review feedback (#8893)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 07:09:28 +00:00
Shaojin Wen
dea5b6da89
refactor(review): extract the toolchain adapter boundary (#8776)
* refactor(review): extract the toolchain adapter boundary

`review build-test` combined three responsibilities in one module: reading
the review plan, deciding which toolchain can be verified deterministically,
and implementing npm workspace installation, affected-package selection,
dependency widening, build execution, test execution, and reporting.

Move the npm implementation behind an internal `ReviewToolchainAdapter`
contract. `build-test.ts` keeps CLI routing, plan reading, output trimming,
env shaping, and the spawn boundary; `lib/npm-toolchain.ts` owns npm
detection and the verification algorithm; `lib/disk.ts` holds the shared
free-disk floors.

This is a move, not a rewrite: 95% of the lines removed from build-test.ts
reappear verbatim in the new files. The CLI arguments, the BuildTestReport
JSON shape, and every npm behaviour are unchanged, and the existing
build-test suite is the compatibility oracle for that.

Selection requires exactly one applicable adapter and fails closed to the
`unsupported` handoff otherwise, so a second toolchain lands as a
registration rather than another branch in this file.

* docs(review): record the toolchain adapter boundary design

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-11 05:47:31 +00:00
顾盼
e20601d6c4
fix(cli): switch completion tabs with bare arrows (#8576) 2026-08-11 02:29:11 +00:00
Shaojin Wen
95e17691a9
chore(serve): remove the /demo debug page (#8805)
* chore(serve): remove the /demo debug page

The daemon has shipped a real browser UI for a while: `resolveWebShellDir()`
finds the bundled Web Shell assets and `mountWebShellAssets()` serves them at
`/`, so `qwen serve` already opens onto a full client. `/demo` stayed behind as
a 663-line inline-HTML console covering the same ground with none of the
reach — nobody drives the daemon through it, and `npm run dev:daemon` starts
the Web Shell dev server rather than the demo page.

Keeping it around costs more than the dead code. It is the only file in the
tree that pairs an event log with daemon HTTP, so work that starts as a Web
Shell observation lands there instead: #8762 was found while running `/review`
through the Web Shell and was fixed entirely inside the demo page's rendering,
with "no Web Shell changes" in its own risk note. Deleting the page removes
that decoy.

Nothing is lost for protocol-level debugging: `GET /session/:id/events`
streams the same raw frames the Events tab printed.

`/health` shared `routes/health-demo.ts` with the demo handler, so the module
is now `routes/health.ts` / `createHealthRoutes()` and drops its `getPort`
dependency. The rate-limit exemption, the boot breadcrumb, and the daemon docs
lose their `/demo` arms; the loopback self-origin shim regression test already
asserted through `/health` and only needed its title corrected.

* test(serve): pin the removed /demo contract and the pre-auth surface

Review follow-up. Three of the removal hunks shipped ungated, and two doc
sentences the removal rewrote were describing the pre-auth surface wrong —
both before and after the edit.

Deleting the `/demo` route took its assertions with it, so nothing failed if
the handler came back: the Web Shell suite only exercised a generic deep link,
and the rate-limit exemption could be widened again with the suite still green.
`/demo` is now pinned as what it became — an ordinary unknown path: a
non-navigation request 404s, a browser navigation is answered by the SPA
fallback like any other deep link, and once a token is configured (with or
without `--require-auth`) that navigation is refused with 401, because the
fallback sits behind the bearer. The rate-limit test pins that `/health` is the
only exempt GET, so re-adding a second pre-auth page to the predicate fails
instead of silently escaping the limiter. Each new assertion was checked by
reverting the hunk it guards and confirming it goes red.

The `--allow-origin '*'` warning and both `--allow-origin` doc paragraphs
enumerated `/health` as the residual tokenless surface and said nothing about
the Web Shell static assets, which are mounted before the bearer in every
launch mode and stay reachable even under `--require-auth` — the enumeration
also claimed `/health` stays pre-auth on non-loopback binds, where it is
registered behind the bearer and 401s. A probe across all three launch modes
established the actual matrix; the warning and the docs now match it and name
`--no-web` as the way to remove the residual browser surface. The warning text
is asserted by a test for the first time.

* fix(serve): correct Web Shell doc claims and re-pin the pre-auth CORS wall

Review follow-up. The removal rewrote the daemon docs around the Web
Shell, and three of the rewritten claims did not match what the runtime
actually does: §1 never said how the bearer reaches the browser (with
auth on, the plain URL loads a shell whose every API call 401s), §8
called the shell writable on any bind (on a non-loopback bind without
`--allow-origin` its POSTs hit the CORS wall and 403), and §8 served
`/session/:id` without the document-navigation qualifier its own code
enforces. The §9 call-chain diagram also still listed the deleted
`/demo` route, the developer flag references had no `--web`/`--no-web`
row despite the new guidance pointing at the flag, and both design docs
listed the JSON body parser ahead of post-auth `/health` while
`createServeApp()` registers them the other way round.

The deleted `/demo` CORS test was also the only assertion that a
pre-auth page sits behind the Origin wall — every surviving Origin test
targets an API path. Re-pin it for the shell root so a mount-order
regression fails instead of exposing the pre-auth HTML surface
cross-origin.

* fix(serve): finish demo rename sweep and scope pre-auth shell claims to loopback

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

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-10 13:31:15 +00:00
jinye
a292c89a3b
feat(cli): add background cleanup for OpenAI API logs (#8862)
* feat(cli): add background cleanup for OpenAI API logs

With model.enableOpenAILogging on, every API call appends a full
request/response JSON under logs/openai with no rotation — heavy usage
accumulates hundreds of thousands of files (tens of GB) within months.

Register a third cleaner in the existing background housekeeping
pipeline that sweeps openai-*.json files older than the new
model.openAILogRetentionDays setting (default 7 days). The
filename-embedded UTC date is used as a fast path to avoid one stat()
per file; the boundary day and unparseable names fall back to mtime.
Throttling is keyed on the resolved log dir, so both the default
per-CWD layout and a shared custom openAILoggingDir are swept at most
once a day. The sweep runs regardless of whether logging is currently
enabled, so residue from earlier debugging sessions still gets cleaned.

Scope note: housekeeping only starts for interactive sessions, so
headless (-p) / SDK processes are not covered yet.

* codex: address PR review feedback (#8862)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-10 12:20:33 +00:00
jinye
fa8cae5418
fix(serve): Allow approved external built-in text writes (#8852)
* fix(serve): allow approved external built-in text writes

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

* fix(serve): keep write provenance off startup bundle

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-10 12:19:58 +00:00
Shaojin Wen
7c89665be1
fix(cli): extend the #8663 loader denylist and harden its scrub lifecycle (#8763)
* fix(cli): extend the #8663 loader denylist and harden its scrub lifecycle

Follow-up to #8663. Its inherited-env denylist closed the NODE_OPTIONS/
NODE_PATH class but left sibling code-execution and TLS-trust-anchor vars
that reach the same #8653 cross-workspace outcome — an untrusted workspace
`.env` is frozen into daemonRuntimeBaseEnv and distributed to every
workspace's session subprocesses.

Denylist additions, split by the PR's own tiering:

- Scrubbed loader tier (INHERITED_LOADER_ENV_KEYS — scrubbed from the
  inherited launch env and rejected from every `.env`/settings.env scope),
  for pure-injection vars with no legitimate operator-shell use:
  OPENSSL_CONF (startup dlopen of an attacker OpenSSL engine),
  NODE_REPL_EXTERNAL_MODULE, npm_config_node_gyp, npm_config_init_module.

- Reject-from-project-`.env` tier (PROJECT_ENV_HARDCODED_EXCLUSIONS —
  rejected from project files, preserved from the shell / home `.env`), for
  vars with a legitimate operator-shell use whose only exposed vector is an
  untrusted project file:
  * TLS trust anchors SSL_CERT_FILE, SSL_CERT_DIR, CURL_CA_BUNDLE,
    REQUESTS_CA_BUNDLE, GIT_SSL_CAINFO (siblings of NODE_EXTRA_CA_CERTS;
    an attacker CA MITMs a session's git/npm/pip/curl traffic).
  * git command-execution family GIT_SSH_COMMAND, GIT_EXTERNAL_DIFF,
    GIT_CONFIG_GLOBAL/SYSTEM/COUNT and the numbered GIT_CONFIG_KEY_<n>/
    GIT_CONFIG_VALUE_<n> pairs (matched by prefix). core/utils/git-branches.ts
    already scrubs these from the repo's own git invocations.
  * node-gyp interpreter selection NODE_GYP_FORCE_PYTHON, npm_config_python,
    PYTHON (run as the build Python during native-addon installs).

Concurrency: the daemon's process.env scrub/restore and the loader-key
rejection reporter were process-global with no guard for concurrent embedded
daemons in one process (a documented supported config). The first daemon's
close() restored loader vars into the shared env, re-poisoning a still-live
sibling's sessions, and dropped its reporter. The scrub is now reference
counted (acquireInheritedLoaderEnvScrub — snapshot on first acquire, restore
only on last release) and the reporter is cleared only when still active.

Test hardening from the same review: pin the daemon-worker scrub breadcrumb
(not just key removal); pin the fast-path settings.env case-folded
hardcoded-exclusion gate; drain the module-global fast-path stash so the
accumulate assertion is order-independent. Docs updated for the new keys.

* fix(cli): keep the loader-scrub process.env access in the serve guard surface

The refcounted acquireInheritedLoaderEnvScrub read/wrote process.env from
config/shared-env-keys.ts, which the serve process.env guard does not scan —
moving the access out of run-qwen-serve.ts dropped its allowlisted count and
failed process-env-guard.test.ts. Pass the env into the coordinator instead so
run-qwen-serve.ts still owns the process.env reference (matching the existing
scrub helpers), and update the allowlist to the new count.

* fix(cli): block GIT_SSH and GIT_CONFIG_PARAMETERS in the project-env denylist

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

* fix(cli): extend the project-env denylist across git exec, TLS, and rc-file tiers

Close the round-2 review findings: block the remaining git
command-execution siblings (GIT_EXEC_PATH, GIT_TEMPLATE_DIR, GIT_ASKPASS,
GIT_PROXY_COMMAND, GIT_EDITOR), the npm/pip TLS trust knobs
(npm_config_cafile, npm_config_ca, npm_config_strict_ssl, PIP_CERT,
GIT_SSL_CAPATH), and the curl/wget rc-file redirects (CURL_HOME, WGETRC)
from project .env files. Freeze the numbered GIT_CONFIG_KEY_/VALUE_ pairs
on reload together with GIT_CONFIG_COUNT, and sync the qwen-serve.md
loader-key enumeration with settings.md.

* fix(cli): harden the project-env denylist and nested scrub snapshot (#8763)

* fix(cli): merge the loader-env scrub snapshot into one pass (#8763)

acquireInheritedLoaderEnvScrub iterated process.env twice (a snapshot
pass, then the scrub); record the originals inside the scrub's single
pass instead. Drop the acquire-time snapshot clear, which the
release-time clear made unreachable defense, and add tests that kill
the previously surviving mutants on the release-time clear, the
test-only reset, and the undefined-value guard.

* fix(cli): block the round-4 exec-redirect env keys from project files (#8763)

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-10 11:27:30 +00:00
顾盼
c0c0352e25
feat(cli): mirror Live Host releases through OSS (#8674)
* feat(cli): mirror Live Host releases through OSS

* fix(cli): harden Live Host OSS mirror fallback
2026-08-10 11:17:49 +00:00
Shaojin Wen
77bd04bd61
fix(acp-bridge): bound live journal replay chunks (#8801)
* fix(acp-bridge): bound live journal replay chunks

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

* test(core): isolate shell retention sidecars

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

* test(integration): cover aggregated live journal replay

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

* test(core): isolate registry sidecars

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

* fix(acp-bridge): keep unmodeled chunk keys out of live journal merges

The merged live-journal entry is rebuilt by spread-merging the first and
last source events, which was only safe because producers happen to emit
exactly {sessionUpdate, content, _meta?} on mergeable chunks. Gate the
merge on that key set so unmodeled data/update fields keep entries
discrete instead of leaking into the aggregate. Also clarify the
live-journal truncation marker: its retained/truncated counts describe
source events, while the limits count replay entries.

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

* fix(acp-bridge): align replay boundaries for discrete and meta-shaped chunks

Turn compaction folded discrete thought chunks (and non-todo-stop-guard
discrete messages) into one text slot with the last chunk's meta, while
the live journal keeps every discrete chunk separate — resyncing from
compactedReplay mis-attributed text across background tasks. Guard both
chunk paths with the same hasDiscreteMessageMeta predicate the live
journal already uses. Also align the merge gate with the shapes the
shared meta builder emits: tolerate update-level timestamp/
serverTimestamp and qwenTranscript.planToolCallId, and treat an
empty-string parentToolCallId as top-level the way the extractor does.
Document that byte-cap truncation drops whole entries, so the retained
tail can be much smaller than the cap, and tighten the integration
assertion that became vacuous once entries merge source chunks.

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

* fix(acp-bridge): merge subagent chunks in live journal replay

SubAgentTracker stamps every streamed subagent fragment with
{ parentToolCallId, subagentType }, but the live-journal merge gate
only modeled parentToolCallId, so subagent chunks stayed discrete and
a high-fragment subagent stream could still trip history_truncated.
Model subagentType as a carried label (like the completed-turn path,
which merges by parentToolCallId alone) and cover the producer wire
shape in the merge tests.

* fix(acp-bridge): preserve TextContent metadata in live journal replay (#8801)

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 09:52:17 +00:00
Heyang Wang
e46586782c
feat: support drag and drop img in web-shell (#8696)
* feat(web-shell): support image drag and drop

Allow Web Shell composers to ingest image files reliably while
preserving the existing multimodal prompt protocol.

- Share ordered image ingestion across desktop and mobile editors
- Support image-only prompts and BMP preview and provider-safe handling
- Preserve queued payloads across retries and uncertain outcomes
- Add lifecycle guards, user feedback, unit coverage, and browser tests

* fix(web-shell): harden image prompt admission recovery

Preserve complete prompt payloads and prevent duplicate or uncertain
delivery states when admission responses race with queue lifecycle
events.

- Correlate admission, queue, and terminal events by prompt ID
- Restore images and input annotations across retry and edit flows
- Bound image reader concurrency and encoded attachment memory
- Reconcile confirmed removals and explain ambiguous queue entries

* docs(web-shell): align image drag design with review fixes

Document the reviewed admission, recovery, and resource invariants.
Keep the design aligned with the hardened Web Shell implementation.

- Record bounded image ingestion and encoded-data budgeting
- Clarify prompt lifecycle correlation and confirmed removal behavior
- Describe annotation restoration and internal action boundaries
- Update focused validation evidence and acceptance criteria

* fix(web-shell): avoid duplicate restored attachments

Skip payload attachments when restoring text is a no-op because the
same prompt text already exists in the composer.

- Restore images and annotations only when their text is inserted
- Preserve image-only restoration regardless of the current draft
- Add regression coverage for duplicate text with attachments

---------

Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
2026-08-10 05:48:17 +00:00
Shaojin Wen
af372e5a21
perf(review): guarantee compose survives a reverse-audit budget stop (#8791)
* perf(review): guarantee compose survives a reverse-audit budget stop

PR #8687 — a 4,269-line cross-worktree git guard — timed out after six
hours and posted nothing, holding ~20 E2E-confirmed Critical bypasses.
The deadline gate worked: it refused round 3 correctly with ~110
minutes and the whole reserve in hand. The tail after the stop was the
killer — a single hand-rolled verification agent re-running a 15-family
shell/git bypass battery with real filesystem E2E consumed all of it,
and the wall hit mid-verification before compose-review ever ran.

The reserve was one number covering "verification + compose + submit",
which is right for a normal per-finding re-trace but wrong for a
security PR where verification cost is unbounded (real E2E per finding)
while compose and submit stay bounded. So a distinct, smaller compose
FLOOR is carved out and the VERIFIER — not the reverse-audit builder —
is gated on it: below the floor `agent-prompt --role verify` refuses to
build (VERIFY BUDGET, exit 4), the findings keep their `— [unverified]`
tag for compose-review to cap, and compose runs. The floor is strictly
below the reserve, so a healthy run reaches the reverse-audit gate first
and never sees it; it is the cover for the one span the reserve cannot
bound.

The prose closes the bypass the gate cannot see: the post-stop tail
verifies only through the gated builder, never a hand-rolled agent, and
invents no fresh re-verification pass for findings already confirmed —
compose and submit are non-negotiable. DESIGN.md records the incident;
the RA budget message and SKILL Step 5 tail are rewritten to match.

* fix(review): close the round-1 gaps in the compose-floor gate

- R2-2 (Critical): the documented `0` escape hatch did not disable the
  verify gate past the deadline — `remainingSeconds` goes negative there
  and `negative >= 0` is false, firing the supposedly-disabled gate.
  verifyBudgetExhausted now returns null the moment the effective floor is
  0, before the comparison. Pinned with a past-deadline case.
- R2-1 (Critical): the gate bounds prompt CONSTRUCTION, not the wall time
  of an already-admitted verifier that then runs a long E2E past the
  floor — and agent-prompt builds prompts, it cannot cancel a running
  agent. The SKILL tail now tells the orchestrator to bound the WAIT: when
  the deadline is within the compose floor and a verifier batch has not
  returned, stop waiting on it, keep its findings unverified, and compose.
  The remaining execution-time cancellation is a harness capability, noted
  as such (same layer boundary as the hand-rolled-agent caveat).
- R2-3: the agent-prompt exit-code help now documents both the BUDGET and
  VERIFY BUDGET exit-4 refusals.
- R2-4: the reverseAuditBudgetMessage test now pins the new tail rules
  (gated verifier only, no hand-rolled agent, no re-verification).
- R2-5: docs/users/features/code-review.md documents the compose floor —
  default, env var, reserve nesting, exit-4 behaviour, zero hatch.

* fix(review): round-2 fixes for the compose-floor gate

- R3-1 (Critical): the verify gate admitted at exactly the floor, where
  the first work crosses below it — the floor is compose-only with no
  margin, so it now refuses at equality (`> floor`, unlike the RA reserve
  which admits at exact cover). Exact-boundary test flipped.
- R3-2 (Critical): the refusal message and SKILL claimed unverified
  findings "post as needing human review", but the confirmed-only rule
  keeps tagged details terminal-only. Reworded to the true contract:
  compose-review caps the verdict and discloses the verification gap; the
  tagged details stay terminal-only; what posts is the earlier rounds'
  confirmed findings plus that gap.
- R3-5: extracted readDeadlineSeconds / readNonNegativeSeconds, shared by
  both gates so the fail-open contract lives in one place.
- R3-3: pinned the verify gate's fail-open branches (malformed/non-positive
  deadline, past-deadline negative remaining, negative-floor fallback).
- R3-4: pinned the floor-minutes rendering (a field swap to remainingSeconds
  would misstate the protected floor).
- R3-7: pinned that a refused verifier writes no budget-stop marker and no
  admission stamp.
- R3-8: pinned validation-before-gate (a malformed verify call under the
  floor throws, not exit 4).

R3-6 needs no change: the SKILL.test pointer<->heading gate already covers
the DESIGN section (a dangling pointer fails it).

* fix(review): round-3 cheap fixes for the compose-floor gate

Low-risk corrections; the two edge-case Criticals (R4-1 broken-plan
masking, shared with the RA gate; R4-2 compose-review relaunch FIX) are
left as follow-ups — noted on the threads.

- R4-4: the readDeadlineSeconds extraction stranded reverseAuditBudgetExhausted's
  contract JSDoc above the helper; moved it back onto the function.
- R4-5: the round-2 "terminal-only, never posted" wording contradicted
  compose-review's own verdict line ("posted, disclosed as unverified") —
  a pre-existing contract ambiguity this PR should not relitigate. Reworded
  the message and SKILL to the invariant both readings share: an unverified
  finding is never treated as a confirmed blocker; the verdict is capped.
- R4-7: "below the N-minute floor" contradicted the exact-equality refusal
  (the gate admits on `> floor`); now "at or below", in the message and the
  user docs.
- R4-3: pinned that a blank/whitespace floor override falls back to the
  default (only explicit 0 disables).
- R4-6: pinned the negative-remaining clamp in verifyBudgetMessage.

---------

Co-authored-by: verify <verify@local>
2026-08-10 02:50:45 +00:00
易良
cc46babf79
feat(desktop): create default workspace on first launch (#8814)
* feat(desktop): create default workspace on first launch

* test(desktop): cover default workspace failure

* feat(desktop): honor QWEN_DEFAULT_WORKSPACE_DIR for the default workspace

* fix(desktop): defer default workspace creation; kill in-flight runtime on stop

Move default-workspace directory creation out of setup into the runtime
start blocking task, so the first touch of ~/Documents (which can raise
the macOS TCC prompt) no longer blocks the main thread. Path resolution
(initial_workspace/default_workspace) is now pure and reports whether the
derived default directory must be created; creation failures surface
through the existing runtime-failed path.

Register the spawned runtime child in a shared pending handle before the
startup wait, so app exit, restart, and generation switches can kill an
in-flight daemon instead of orphaning it in its own process group. The
startup wait loops poll the shared handle and treat a taken child as a
stop during startup.

* fix(desktop): close runtime startup cancellation race

* fix(desktop): preserve default workspace retries
2026-08-10 02:44:08 +00:00
Shaojin Wen
b314d01f2d
fix(web-shell): stop rendering unrecognized daemon events in transcripts (#8812)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
* fix(web-shell): stop rendering unrecognized daemon events in transcripts

The daemon UI normalizer projects any frame it has no case for into a
`debug` event carrying a raw JSON dump. webui's ChatViewer drops those
blocks, but Web Shell renders `status` and `debug` together as system
info, so every event kind the daemon ships ahead of the UI surfaces as
unreadable JSON in the middle of the conversation. This has been patched
per-symptom three times now: two string-prefix suppressions inside
`isIgnoredWebShellStatus`, plus #8790 for `usage_update`.

Give the normalizer's debug events a structured `debugReason` and let
Web Shell branch on it instead of pattern-matching text:

- `unrecognized_event` / `unrecognized_session_update` — the daemon runs
  ahead of this client; developer diagnostics, not conversation content.
  Web Shell no longer renders them.
- `malformed_payload` — a frame the client does know arrived unusable.
  That is a real defect signal, so it stays visible.

Debug events dispatched by clients themselves, such as Web Shell's own
model-switch summary, carry no `debugReason` and keep rendering.

The two `(unrecognized daemon event)` prefix checks are now covered by
`debugReason` and are removed; the `Model switched: ` check stays, since
`model.changed` projects to a `status` block rather than a debug one.

* fix(sdk): classify a discriminator-less session_update as malformed

Review of #8812 caught a hole in the new classification: `session_update`
payloads such as `{}` or `{ sessionUpdate: 42 }` reach the default branch
with `kind === undefined`, and stamping them `unrecognized_session_update`
made Web Shell hide the only diagnostic a malformed frame produces.
Reserve the unrecognized reason for a real unknown string kind.

Also update the top-level default-case comment, which still pointed
adapters at the debug text prefix, and add a reducer-level test proving
`debugReason` survives the UI-event → transcript-block boundary: the
normalizer tests inspect events and the Web Shell tests build blocks by
hand, so dropping the spread in transcript.ts would leave both green.

* fix(web-shell): keep filtering legacy debug blocks, tighten the reason split

Four review findings from #8812:

- `WebShellTranscript` is a public entry point taking already-projected
  blocks, so blocks from an SDK predating `debugReason` still arrive with
  no reason and started rendering again when the prefix checks were
  removed. Fall back to the stable ` (unrecognized daemon event): ` marker
  when no reason is present — which covers every unrecognized event type,
  not just the two previously suppressed by name. The old-shape fixture is
  restored (adding `debugReason` to it had hidden this path) and a
  dedicated legacy test now pins it.
- A whitespace-only discriminator is truthy, so `sessionUpdate: ' '` was
  classified unrecognized and hidden. Gate on `trim()`, matching the
  convention `getFirstString` already uses.
- Add the mirror invariant for the reducer: a client-dispatched debug
  event must produce a block with no `debugReason`. Defaulting the field
  in `appendStatusBlock` otherwise passes every other test while tagging
  the model-switch summary unrecognized.
- Guard the outermost public re-export. A type-only guard would not hold —
  vitest erases `export type` through esbuild and this package's tsconfig
  excludes `test/` — so ship the union as `DAEMON_UI_DEBUG_REASONS`,
  matching `DAEMON_ERROR_KINDS`, and assert it at runtime.

* fix(web-shell): suppress legacy usage_update/a2ui blocks with no debugReason

Follow-up verification on #8812 pointed out the marker fallback does not
close the original report. #8790 stopped the SDK inserting new
`usage_update` blocks, but `WebShellTranscript` renders whatever blocks its
caller passes, so a transcript persisted or projected before that still
holds them and the spam returns after upgrade.

The legacy `session_update` projection is `<kind>: <json>` with no marker to
key on, so match those by kind name instead. The list is closed on purpose —
`usage_update` and `a2ui`, the two known to have leaked — and requires the
`: {` shape, because a generic `<word>: {` rule would swallow legitimate
diagnostics. Blocks the normalizer classified still win on `debugReason`,
so `malformed_payload` and client-dispatched debug blocks stay visible.

Mutation-checked in both directions: dropping the fallback fails the legacy
test, and loosening the prefix to bare `usage_update:` fails the test that
pins prose and classified blocks staying visible.

* fix(web-shell): match the legacy projection shape, not a quoted marker

The legacy fallback was too broad in two ways, both reachable. It ran for
`status` blocks as well as `debug` ones, because this helper is called from
the shared `case 'status': case 'debug':` arm, and it matched the marker as
a substring anywhere in the text.

Probed at df0b757c3c: all four of these were dropped — a status line
quoting the marker, a legacy malformed payload relaying an upstream peer's
message that contains it, a client-dispatched summary quoting it, and a
status block whose text starts with `usage_update: {`. Exactly the
diagnostics this PR promises to keep.

Scope the text match to `debug` blocks, and anchor it to the whole legacy
projection (`<event-type> (unrecognized daemon event): <json>`) instead of
the bare marker. Classified blocks still win on `debugReason` before any of
this runs.

Mutation-checked: dropping the `kind === 'debug'` guard and restoring the
substring match each fail the new negative test.

* fix(web-shell): match legacy projections with non-object payloads

The anchored legacy pattern required the payload to start with `[`, `{` or
`"`, which only holds for objects and arrays. `DaemonEvent.data` is
`unknown`, and `stringifyJson` returns strings verbatim, serializes
primitives as `42` / `true` / `null`, and yields `''` for `undefined` — so
every non-object payload bypassed the compatibility fallback and rendered.

Drop the leading-character constraint. The event-type prefix plus the fixed
phrase, anchored at the start, is specific enough on its own, and the
negative cases for quoted markers and status blocks still pass.

Regression test covers object, array, string, number, boolean, null and
empty payloads. Mutation-checked: restoring the character class fails it.

* fix(web-shell): hide debug blocks by the unrecognized_ reason category

The debugReason filter enumerated the two current `unrecognized_*` values,
but the SDK contract this PR adds names reasons by category: `unrecognized_*`
is forward-compat noise to hide, `malformed_*` a defect signal to keep
visible. A reason a newer SDK adds would compile silently against the
two-literal comparison and render raw JSON again with both suites green.

Match the category prefix instead. Also drop the now-dead marker branch in
MessageList's mid-turn hide check: every block carrying that prefix is
filtered upstream in the adapter (reason-stamped via `debugReason`, legacy
via the anchored pattern), and the dedicated normalizer case emits a status
event keyed by `source`. Document `DaemonUiDebugReason` beside the sibling
closed enums in the daemon-ui docs, whose forward-compat bullet still
described the unstamped projection.

Regression test pins both directions of the category contract with reasons
outside the current enum.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-10 00:51:57 +00:00
jinye
0a3d7bb5c1
feat(acp): Protect against repeated tool execution failures (#8469)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(acp): protect repeated tool execution failures

Add a conservative prompt-local guard for repeated typed ACP tool execution failures, with shadow/warn/enforce rollout modes, privacy-safe telemetry, and coverage for the final execution outcome contract.

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

* fix(acp): harden repeated tool failure guard rollout

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

* fix(acp): address repeated failure guard review

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

* fix(acp): improve repeated failure guard recall

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

* docs(acp): clarify review and rollout gates

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-09 15:47:38 +00:00
jinye
a810f7e16c
fix(serve): Make session restore timeouts safe and observable (#8691)
* fix(serve): make session restore timeouts safe

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

* fix(cli): restore missing core mock exports in the ACP worktree suite

The restore-tracing change added `extractDaemonTraceContext` and
`withDaemonSpan` to `acpAgent.ts`, but `acpAgent.worktree.test.ts`
replaces `@qwen-code/qwen-code-core` with a full mock factory that never
listed them. `loadSession` then failed on an undefined export, taking all
three cases down and producing teardown rejections from the half-built
agent. The sibling suite was updated; this one was missed.

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

* fix(serve): bound and disambiguate the abandoned restore lifecycle

Four follow-ups from review of the restore timeout work.

A startup budget may now raise the restore budget but never lower it.
Taking an explicitly configured `initializeTimeoutMs` as the restore
fallback meant a deployment that tightened its child-initialize check
still inherited a sub-default restore deadline — exactly the failure this
change exists to remove. An explicit `sessionRestoreTimeoutMs` still wins
outright, including below the default, for deployments that want restore
to fail fast. Validation now names the field actually at fault.

A restore fenced behind a timed-out predecessor is no longer reported as
an ordinary in-flight restore. It carries `reason:
awaiting_abandoned_cleanup` and a retry hint of one restore budget
(capped at 120s) instead of the ordinary 5 seconds, because the fence
cannot clear until the non-cancellable ACP request settles and a 5-second
cadence just spins the caller against a 409 it cannot resolve.

Whether a channel is condemned is now derived rather than sticky. A
timeout recorded `emptyReapPending` permanently, so any channel that had
ever seen one was guaranteed to be reaped once its remaining work
drained, forcing a cold respawn even when the late restore had landed and
closed cleanly. The reap condition is now computed from an outstanding
`unsettledAbandonedRestores` set, quarantine, or an ordinary pending
empty reap; real settlement clears the entry and hands the channel back
to the configured idle policy.

Abandonment no longer retains ownership without bound. One further
restore budget after the deadline, a still-unsettled restore marks the
channel `restoreSettlementOverdue`: existing sessions and workspace
control keep working, but fresh session work is refused so the channel
can drain, since closing the transport is the only lever that releases a
permanently hung request. Releasing capacity while hidden work runs would
allow unbounded oversubscription, and force-killing a channel with live
siblings would reintroduce the failure this work removes, so neither is
done. Fresh-admission blocking is now scanned across alive channels
rather than tracked in a single reference, so a second condemned channel
cannot silently displace the first.

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

* fix(serve): keep the abandoned restore lifecycle off ids it no longer owns

Two correctness gaps in the abandoned-restore machinery introduced by this
PR, both reported by automated review and both confirmed by mutation
testing (each new test fails when its fix is reverted).

A caller-supplied `sessionId` is used verbatim by the agent, but
`spawnOrAttach` never consulted `inFlightRestores`. A fresh spawn could
therefore take an id that a restore still owns, in either lifecycle phase.
The consequences were silent: `abandonedRestoreIds` suppresses session
updates, guardrail events, and child notifications, so the new session
would have registered successfully and then emitted nothing; and a late
`settleAbandonedRestore` would have closed and tombstoned it out from
under its owner. Such a spawn is now rejected with the same
`RestoreInProgressError` and reason the restore path uses, so the caller
gets the correct retry hint for whichever phase is holding the id.

The cleanup path is guarded independently, because the request-level check
only covers the id the caller asked for and a session registers under the
id the child returns. An abandoned restore never reaches
`createSessionEntry` — the deadline rejects before registration — so any
live entry under that id belongs to someone else. Cleanup now detects that
and returns without closing or tombstoning, releasing its own bookkeeping
instead.

The notification fence has no TTL and was only cleared by
`markRestoreInFlight`, which covers a subsequent restore and nothing else.
`createSessionEntry` now clears it for every registration route, so a
legitimate owner of the id is never handed a session that silently drops
everything the child sends it.

Also tightens two tests that could not observe the values they pin. The
SDK default restore timeout admitted any value in (30s, 70s]; it is now
split at the exact boundary, so collapsing the default onto the 60s server
budget — which would make the client abort race the daemon's own deadline
and cost the caller its structured 504 — fails. And the advertised-budget
propagation from capabilities through to the SDK call had no live-path
assertion; dropping the capabilities argument at the real call site left
every existing test green. The `as never` casts are replaced with typed
`DaemonCapabilities` values so a field rename fails typecheck.

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

* fix(serve): let a condemned channel drain without its wedged child

Merging main's active-work close protocol (#8588) into this PR's abandoned
restore bound produced a deadlock that neither side has on its own, and the
conflict resolution was committed without running tests.

`maybeCloseIdleSession` now routes through `confirmChildUnheld`, which asks
the child whether it still holds work before closing a session nobody is
attached to. That is right in general and wrong for a channel this PR has
already condemned. `restoreSettlementOverdue` and quarantine exist precisely
because the child stopped being answerable, and their whole premise is that
visible work drains so the channel can be reaped — closing the transport is
the only thing that can release a restore we cannot cancel. Making that
drain depend on a round trip to the wedged child inverts it: a child stuck
in a non-cancellable restore is exactly the one that cannot reply inside
`ACTIVE_WORK_CLOSE_TIMEOUT_MS`, so the sessions never close, the channel
never drains, the reap never fires, and the bound never takes effect.

A channel condemned by the restore lifecycle now skips the round trip and
proceeds to local teardown. Nothing is attached to the session by then —
`maybeCloseIdleSession` gates on that — and the sibling-safety invariant is
untouched: this closes sessions whose clients have already left, it does not
force-kill a channel that still has live ones.

The regression test drives an overdue channel whose child never answers the
close-if-unheld probe and asserts the detach still reaps it. Reverting the
guard reproduces the deadlock as a test timeout.

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

* test(serve): pin the restore-timeout contract the review found unasserted

Automated review identified eleven places where the restore-timeout work's
behavior was correct but unpinned — each with a mutation that ships green.
Every fix below was verified the same way: apply the mutation, watch the new
assertion fail, revert, watch it pass.

The timeout path's telemetry had no coverage at all, which is the sharpest
gap given that observability is what this work exists to deliver. A shared
recorder now asserts the public timeout result and its kill_empty-vs-
fence_shared signal, the late arrival, and the cleanup outcome for both the
closed and quarantined cases.

The deadline timer's cancellation on a successful restore was likewise
unpinned: deleting both `clearTimeout` calls kept the whole suite green,
while in production the stale timer fires one budget after a successful
restore and abandons a live session — fencing its frames, closing its event
bus, and emitting a spurious timeout. A success-path test now advances past
the deadline and asserts no second public result.

Three more bridge assertions proved less than they claimed: the concurrent-
restore case never checked that the abandoned restore settles, the
workspace-control case never checked that the deferred reap eventually
fires, and the resolver never pinned the accepting side of the MAX boundary
(a `>` to `>=` mutation rejects the largest legal delay at boot). The
workspace-control case also needed a positive channel idle budget, since
with the default zero the idle-timer kill substitutes for the reap junction
under test; its assertions are rewritten around the derived reap semantics
rather than the sticky flag they predate.

Outside the bridge: the scheduled-task timeout wiring had no test, so
deleting the arguments silently fell back to the helpers' own defaults; the
cold restore path never asserted that `live_restore_ms` is absent; the SDK's
per-request validation and its over-ceiling clamp were untested; the WebUI
watchdog test jumped straight to its own value, staying green for any
watchdog at or below it, including the 30s attach value that would recreate
the original symptom in the browser; and the two new known error types were
unexercised, so dropping either would relabel every restore-timeout and
quarantine error as unknown.

Two review items are deliberately not taken here and are recorded in the
design doc's non-goals instead: transcript materialization is still not
separately attributable from `config_setup`, which needs instrumentation
inside the core session loader that P1/P2 restructures anyway, and sibling
event-loop latency during a large restore remains unmeasured.

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

* fix(serve): bound the condemned-channel close and complete the fence contract

Second automated review round, on the code the first round produced. One
Critical and twelve suggestions; all verified by mutation before and after.

**The Critical is a regression I introduced.** Letting a condemned channel
skip the bounded hold probe routed it into `closeSessionImpl`, whose agent
close is unbounded when it throws on failure — so the fix traded a bounded
wait on a wedged child for an unbounded one. A settlement-overdue channel
with an unresponsive child would hang `detachClient` forever, strand the
session in `closing`, never drain, never reap, and 503 every new session
until restart: strictly worse than before. `CloseSessionOpts` now carries an
`agentCloseTimeoutMs` that the condemned path sets, so a hang lands in the
existing unknown-outcome recovery, which kills the channel — the teardown
the drain was waiting for. The earlier test missed this because its fake
child still answered the plain close; it now answers nothing at all, and
asserts the detach itself returns.

**The fence was invisible on the transports clients actually use.**
`toRpcError` had no `RestoreInProgressError` case, so over acp-http and
acp-ws — which SDK negotiation prefers over REST — the fence degraded to an
opaque internal 500 with no code, reason, or hint, and the backoff contract
this work documents was impossible to honor.

**Two retry hints still advertised five seconds for states that outlive a
budget.** The restore 504 creates the fence, and quarantine lasts until the
channel drains; a fresh-id caller never reaches the 409 that carries the
real hint, so its header was the only signal it got. Both now derive from
the budget through one shared clamp helper, which also replaces the formula
that was inlined in the bridge and gives the documented 5-120s bounds a
test.

**A spawn collision reported an operation the caller never issued**, naming
the restore owner's action as both the active and the requested one and
telling the caller to retry an endpoint it never called.

The rest: five places still described the initialize-timeout fallback as a
plain chain rather than raise-only, contradicting sibling docs shipped in
this same PR; the design doc omitted the retry-hint clamp; the protocol
reference omitted the new spawn emission site; the error taxonomy omitted
`restore_settlement_overdue`, which matters because its audience is
monitoring. Test-only gaps: the dynamic 409 had no HTTP-layer coverage, the
120-second cap was unpinned, and the SDK's precedence of an explicit global
timeout over the advertised budget was pinned only branch-by-branch.

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

* fix(serve): preserve restore session ownership handoff

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-09 15:47:09 +00:00
易良
bf84caf173
feat: add Local Control pairing to CLI and Desktop (#8727)
Some checks failed
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
npm cache producer / Save npm cache (push) Has been cancelled
* feat(cli): add Local Control pairing

* fix(cli): address Local Control review feedback

* fix(cli): allow Local Control loopback origin

* feat(desktop): add Local Control pairing

* fix(local-control): bound unauthenticated connections

* test(desktop): allow Windows proxy cleanup

* test(desktop): avoid socket cleanup timing

* fix(desktop): surface Local Control status

* fix(desktop): simplify Local Control window

* fix(desktop): harden Local Control pairing

* fix(desktop): bind Mac wake lock to app
2026-08-09 08:31:54 +00:00
jinye
60458f5e37
fix(serve): Coordinate caller-supplied session IDs (#8415)
* fix(serve): coordinate caller-supplied session IDs

Complete daemon-wide admission across REST, ACP, workspace generations, SDKs, and MCP.

Closes #8411

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

* test(serve): wire session bridges in hot-reload harness

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

* fix(serve): address review round for caller-supplied session IDs (#8415)

Restore the observability and fail-loud guarantees flagged in review:
log every session-id admission routing failure, name the live foreign
owner workspace in restore conflicts, make the ACP dispatcher's
admission dependency required so load/resume cannot run on a mount
without one, and require mountAcpHttp hosts to inject the daemon-wide
admission instead of silently building a weak fallback. Harden the SDK
WS transport against environments without global fetch and against
non-capabilities 200 envelopes, and align the design doc with the
implemented restore-sharing and persistence-failure semantics.

* fix(sdk): harden session ID capability fallback

Preserve REST capability errors, fail closed on malformed envelopes, retain restore routing diagnostics, and align retry documentation.

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

* fix(serve): normalize restored session IDs

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

* fix(session): preserve mixed-case legacy session access

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@service.alibaba.com>
2026-08-09 07:31:30 +00:00
callmeYe
39377fcff3
feat(daemon): add batch skill toggle API (#8664)
* feat(daemon): add batch skill toggle API

* test(serve): update capability integration baseline

* fix(daemon): apply skill batches atomically

* test(daemon): pin Skill batch toggle contracts and fix docs examples

* test(daemon): pin Skill batch toggle mutants flagged in review

* test(daemon): cover Skill batch toggle edge cases

* docs(daemon): clarify Skill batch toggle contract notes from review

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

* test(daemon): pin Skill batch toggle cap semantics and SDK surface shape

* test(daemon): pin Skill batch toggle mutants flagged in round-5 review

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-08 23:22:04 +00:00
易良
1a5d1c445e
fix(core): confirm read-only git commands when repo config executes programs (#8575) (#8645)
* fix(core): confirm read-only git commands when repo config executes programs (#8575)

Whitelisted read-only git sub-commands (status, diff, log, show, ...) are
auto-approved based purely on command text, but git can execute programs
configured in the repository-local config while running them: diff.external,
core.fsmonitor, core.pager / pager overrides, diff driver textconv,
core.askpass, credential.helper, core.sshCommand, remote proxies,
ext:: remote URLs, gpg.program. A planted .git/config could turn an
auto-approved command into arbitrary code execution.

Add a synchronous repo-local config probe (bounded stat walk + small file
reads, fail-closed) shared by the AST and regex classifiers: when a git
command would classify as read-only and the repo-local config reachable
from the execution cwd contains program-executing keys, the verdict is
downgraded so the command requires confirmation. Global/system config is
deliberately out of scope (the user's own setup, not a cloned-repo
attack surface). All permission entry points (shell tool, monitor tool,
permission manager, memory-scoped agent policy) now pass the execution
cwd to the classifier. Classifier APIs only gain an optional parameter;
behavior without cwd is unchanged.

* fix(core): close two probe gaps from review of #8575

- Speculation gate now receives the execution cwd: speculated shell
  calls bypass the permission flow, so evaluateToolCall passes cwd
  (and the shell directory arg, which takes precedence) into
  classifyShellCommandSafety. A speculated `git diff` in a repo with
  diff.external planted now hits the boundary instead of executing.
- Probe reads `.git/config.worktree` of the main checkout too — with
  extensions.worktreeConfig enabled git reads it for the main
  worktree, so a key planted there no longer bypasses the probe.
- plan-mode shell policy passes its effective cwd to the classifier
  for consistent classification (no execution hole there; consistency).
- Document bare repos as out of scope.
- Add end-to-end integration test driving the real probe + classifier
  through ShellToolInvocation.getDefaultPermission (no fs mocking).

* fix(core): honor Git worktree config semantics

* fix(core): fail closed on opaque git config constructs (#8575)

Round-3 hardening of the config probe, closing bypasses found in local
security review (all empirically reachable via attacker-written
.git/config):

- Section headers the minimal parser cannot interpret (e.g. `]` inside a
  quoted subsection) now fail closed instead of silently dropping the
  entries beneath them.
- Inline `[section] key = value` lines are parsed instead of discarded.
- Unparseable `.git` pointer files fail closed like unreadable ones.
- include/includeIf entries are flagged rather than resolved: their
  targets can live outside `.git` (e.g. tracked working-tree files).
- core.gitProxy added to the program-valued keys (git:// transport via
  whitelisted `git remote show`).
- Document the cd-into-another-repo limitation in the module doc.
- Add the missing PermissionManager cwd-threading contract test
  (dirty repo config → ask, clean → allow) and regression tests for
  each behavior above.

* fix(core): track cd in git config probe; close filter/url bypasses (#8575)

Round-4 hardening from the local security/correctness review — each item
was empirically demonstrated against the prior head:

- Compound commands now track cd/pushd/popd: statically resolvable
  targets move the probe's base directory (same-repo `cd subdir` stays
  read-only), unresolvable targets (`cd`, `cd -`, `cd $VAR`, `popd`,
  quoted/expanded targets) downgrade later git segments. Closes the
  `cd <dirty-repo> && git status` bypass in both the AST and regex
  classifiers, including tree-sitter's nested-list chains.
- filter.<name>.clean/smudge/process flagged: `git diff` runs worktree
  content through the configured clean filter with no extra flags.
- url.<base>.insteadOf rewrite targets starting with ext:: flagged
  (combined with protocol.ext.allow in the same file this executes on
  whitelisted `git remote show`).
- Config reads are size-capped at 1 MiB and fail closed above it (DoS
  guard for the synchronous permission path).
- Boolean pager overrides (pager.<cmd> = true/false) no longer flagged.
- Added the missing wiring contract tests: PermissionManager
  config.getCwd() fallback, memory-scoped agent shell policy, plan-mode
  shell policy (including the directory-param override).

* fix(core): provide getTargetDir in speculation test mocks (#8575)

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

* fix(core): harden git-config exec probe against cd-tracking bypasses (#8575)

Address review round 1 findings on the repository-local git config
execution probe:

- Track cd/pushd across every sibling-statement sequence (program, brace
  group, subshell body), not only `&&` lists; propagate the directory out
  of brace groups and redirected/negated wrappers.
- Respect list-operator semantics: cd state no longer leaks across `||`
  or `&`, and non-`&&` sequential statements keep the prior directory in
  the safety equation.
- Resolve cd targets strictly: skip flag arguments (`-P`/`-L`/`-e`/`--`),
  reject flag-only and multi-operand forms, accept only statically
  unquotable word/string/raw_string targets (no concatenation, ANSI-C
  quoting, backslash escapes, or expansions), and fail closed when the
  target is missing or not a directory.
- Probe git discovery more faithfully: resolve symlinks (realpath), treat
  a directory that is itself a git directory (bare repos, submodule
  storage) as a repo, fail closed when the search-depth budget exhausts,
  decode config values and subsections the way git does (quoted-segment
  concatenation, escapes), and add diff.<driver>.command and
  core.alternateRefsCommand to the program-valued keys.
- Scope fixes: fall back to the scoped execution root when the memory
  agent shell probe has no cwd; resolve compound-command defaults against
  the full command so a segment rule cannot override the cd-aware verdict;
  keep sub-commands after a directory change in the confirmation scope for
  both the shell and monitor tools.
- Tests: regression coverage for every fix plus mutation-checked wiring
  tests; skip the chmod-based EACCES simulation on Windows/root; use a
  relative submodule gitdir pointer; parametrize filter clean/smudge/process.

* fix(core): close round-2 review findings for git-config exec probe (#8645)

* fix(core): flag deprecated dot-form git config sections in exec probe (#8645)

* fix(core): probe core.hooksPath targets in git-config exec probe (#8645)

core.hooksPath was listed in PROGRAM_VALUED_KEYS, so any repo with the
key set — every husky/lefthook install, and the worktrees Qwen itself
creates — downgraded all whitelisted read-only git commands to ask.
The key names no program, it only redirects hook lookup, so resolve it
the way git does (~ expansion, relative anchored at the worktree root)
and probe the target directory for executable read-only-triggered hooks
exactly like the default hooks directory.

* fix(core): probe submodule storage configs in git-config exec probe (#8645)

* fix(core): tighten git config safety checks

* fix(core): address verification findings for git-config exec probe (#8645)

* refactor(core): reset git config probe to issue scope

* fix(core): use Git config semantics for read-only probes

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-08 23:12:14 +00:00
易良
1cbf2e8fc7
feat(ci): auto-assign issues to area owners from labels (#8668)
* feat(ci): auto-assign issues to area owners from labels

Route labelled issues to a maintainer with push access, without putting
issue text in the path of a write token.

Assignment is a pure function of the issue's labels and a checked-in
label -> owner map, evaluated by a standalone workflow on issues:labeled.
No model runs in the assignment path and the script never reads issue
title, body, or comments, so untrusted issue text cannot select an
assignee. Owners come from CODEOWNERS (asserted by test) and every
candidate is re-checked against the collaborator permission API before
the write, so editing the map cannot grant access. Among eligible owners
the least loaded wins, rotating by issue number to break ties.

* fix(ci): decouple issue owner map from CODEOWNERS

CODEOWNERS answers who owns a code path, which is narrower than who may
be assigned an issue in an area: the repository has ~44 collaborators
with push access against 7 CODEOWNERS entries, so the membership test
would have rejected legitimate additions such as admins and maintainers
who own no path.

Drop that assertion and document the actual process for adding owners.
The live collaborator permission check remains the boundary. Add the
validation the map does need: duplicate owners would skew load balancing,
and duplicate area names would silently shadow each other under
first-match-wins.

* fix(ci): keep issue ownership triggers disjoint

* fix(ci): recheck issue owner assignment before write

* test(ci): cover issue owner label recheck

* docs(ci): Correct CODEOWNERS count in issue assignment rationale

* fix(automation): preserve autofix issue ownership

* feat(ci): widen core issue owner pool to active repository maintainers

* feat(ci): add four more collaborators to core issue owner pool

* fix(ci): tighten issue-owner map validation and sync trigger docs

* fix(ci): tighten issue-owner assignment tests and login validation (#8668)

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-08 23:01:03 +00:00
callmeYe
33b124321c
feat(core): support Qoder plugin extensions (#8661)
* feat(core): support Qoder plugin extensions

* fix(core): address Qoder extension review feedback

* fix(core): handle annotated tags and unsafe parse errors

* fix(core): harden Qoder conversion edge cases

* fix(core): sanitize Qoder conversion inputs

* fix(core): address Qoder extension round-3 review feedback

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

* fix(core): honor explicit marketplace selection over Qoder manifest

* fix(core): preserve nested plugin update provenance

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-08 22:35:54 +00:00
Shaojin Wen
bb8f2c0129
fix(cli): scrub inherited loader env vars from daemon session subprocesses (#8663)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* fix(cli): scrub inherited loader env vars from daemon session subprocesses

Daemon-mode sessions bound to one workspace inherited loader-affecting
env vars (NODE_OPTIONS with dev-harness --import hooks, NODE_PATH,
preload-class vars) from whatever shell launched the daemon, so
subprocesses in another workspace resolved modules through the
launching checkout's tree (fixes #8653).

Scrub the loader subset of RELOAD_EXCLUDED_KEYS from process.env at the
two process boundaries that host sessions: the daemon after freezing its
boot env (the frozen copy keeps loader vars so dev-mode ACP children can
still boot), and the ACP child after the relaunch/sandbox handoff (the
respawned child re-scrubs itself).

Fixes #8653

* fix(cli): reject loader env keys in initial .env load and log scrubs

A trusted workspace's .env could re-populate the loader-key slots that
scrubInheritedLoaderEnv() emptied in the daemon process, because
canApplyParsedEnvKey applied RELOAD_EXCLUDED_KEYS only on reloads.
Reject the loader subset on every .env application path so one
workspace's loader hook cannot reach other workspaces' session
subprocesses through the shared daemon env.

Also make the scrub return the removed keys and emit a stderr
breadcrumb naming them at both boundaries, so a session subprocess
missing an inherited var can be traced back to the scrub.

* fix(cli): reject loader env keys in serve fast path before env freeze

* fix(cli): deny npm_config_node_options and report rejected loader keys

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

* fix(cli): match loader env keys case-insensitively and report settings.env rejections

* fix(cli): canonicalize loader env key spellings and scrub channel daemon workers

npm maps non-leading underscores in npm_config_* keys onto hyphens, so
npm_config_node-options injected NODE_OPTIONS exactly like
npm_config_node_options while slipping past every loader gate and scrub.
Canonicalize case and underscore/hyphen spelling on both sides of the
loader-key membership test, covering .env loads, settings.env
application, the serve fast path, and the inherited scrubs.

Channel daemon workers are spawned with the daemon's pre-scrub base env
but are not ACP children, so they never ran the self-scrub; mirror the
ACP-child scrub at the worker entry so nothing a worker spawns inherits
loader vars into another workspace.

Scope the settings.env rejection warning per workspace so a
multi-workspace daemon reports every workspace's rejection instead of
deduping them all under one label, revert the unread
loadServeFastPathEnvironment return value to void, and pin the
buildRuntimeEnvironment settings.env gate and the consume-once stash
reset with discriminating tests.

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

* fix(cli): address review findings for loader env denylist

- report rejected loader keys through the daemon log after boot (per-workspace .env loads were silent once boot stderr was gone)

- accumulate serve fast-path rejected keys across loads instead of overwriting, dedupe, and use normalized env file paths for rejection sources

- restore scrubbed inherited loader env vars on embedded runQwenServe close()

- add regression tests for ENV scope coverage, reporter dedupe, fast-path accumulation, and post-boot daemon-log diagnostics

- scope docs: top-level env rejection does not apply to mcpServers[].env / hooks[].env; document serve loader-scrub behavior

* fix(tests): add writeStderrLineSafe to stdioHelpers mocks and sync env guard allowlist

* fix(cli): scrub loader vars from the daemon base env and retighten the denylist

The frozen daemonRuntimeBaseEnv was captured before the launch-env scrub,
so daemon-spawned session processes still booted under the inherited
loader — the child-side post-boot scrub ran after Node had already
consumed NODE_OPTIONS. The base env is now scrubbed before the freeze
(except under the DEV=true harness, whose .ts entries need the tsx
loader), and close() restores the host's launch env from a pristine
snapshot.

Denylist scope now follows the injection-vs-search-path split: adds the
npm config-file redirect keys, ZDOTDIR, and a BASH_FUNC_* prefix rule;
moves ENV/LD_LIBRARY_PATH/DYLD_LIBRARY_PATH back to their reload-only
tier (mainstream toolchain compatibility); blocks QWEN_CLI_ENTRY and
NODE_EXTRA_CA_CERTS from project .env files. The ACP-child scrub is
gated on the daemon stamp (QWEN_CODE_SERVE) so direct editor ACP
integrations keep the user's exported environment, and the daemon's
per-workspace .env rejections are now reported from
buildRuntimeEnvironment.

* fix(cli): block DEV spoofing, case-insensitive env exclusions, serve boot env restore (#8663)

Address round-6 review: DEV joins the hardcoded project-env exclusions so a
workspace file cannot disable the daemon's loader-env scrub; the hardcoded
tier is enforced case-insensitively (Windows env lookup is case-insensitive)
via isHardcodedProjectEnvExclusion at every application gate; runQwenServe's
catch restores the scrubbed launch env and detaches the rejection reporter
when startup fails after the scrub. Tests gain the matching regressions,
home-env hermeticity, source-scoped warning filters, and tmpdir cleanup; the
unreachable reload delete-pass loader guard and its vacuous test are removed.

* fix(cli): match the reload-excluded env tier case-insensitively too

Round-6 follow-up: R6-3 named RELOAD_EXCLUDED_KEYS.has() among the gates a
case variant slips, but the hardcoded-tier fix left the reload-only keys
(QWEN_SERVER_TOKEN, PATH, HOME, TMPDIR, …) on exact-case matching. On
Windows a lowercase twin names the same OS variable, so a mid-session
settings.env/.env edit could still rotate the daemon token or move PATH
through a case respelling. Fold the reload tier the same way and pin it
with a reload-behavior regression test. Also note DEV in the settings.md
exclusion docs.

* test(cli): redirect HOME in environment.test.ts for full home-env hermeticity

The source-scoped warning filters fixed the warning-count assertions, but
the process.env assertions (e.g. 'never applies entrypoint or trust-anchor
keys') still read state a real home .env can pollute: home scope
deliberately bypasses the hardcoded exclusions, so a dev machine with
QWEN_CLI_ENTRY in ~/.env applies it and fails the test while CI stays
green. Redirect HOME/USERPROFILE to an empty temp dir in beforeEach —
verified by running the suite with HOME pointed at a poisoned home.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-08 16:11:39 +00:00
jinye
59b750fc4d
feat(serve): Expose active work state (#8588)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(serve): expose active work state

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

* refactor(serve): rebuild active-work reporting on channel-wide snapshots

Reworks the active-work signal after review. Three changes of substance.

Drops the 45s heartbeat watchdog entirely. It inferred "this channel is
dead" from "one Session stopped reporting" and killed the whole channel,
taking every Session on that process with it — including on a suspend,
a long event-loop stall, or a single dropped notification. Channel
liveness is a transport concern and gets its own mechanism.

Replaces the per-Session boolean with a channel-wide snapshot of named
holds, derived on every report from the owners of the work (the
registry's unfinalized set, the notification queue) rather than from a
ledger kept alongside them. Full snapshots make a dropped report
self-correcting in both directions, and a Session's absence from one is
positive evidence the child released it. Agent holds now use
hasUnfinalizedTasks()'s predicate, closing the cancel to
finalizeCancelled() window where a cancelled agent looked idle and its
terminal notification could be stranded.

Leaves prompts out of the child's report: the daemon accepts, queues,
dispatches, and settles them, so its own count is authoritative and
covers the FIFO wait the child cannot see. A snapshot is flushed ahead
of the prompt response so a hold the prompt left behind is on the wire
before the daemon drops that count.

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

* feat(serve): confirm idle before closing, and grade the health signal

Completes the active-work rework with the two facts a restart controller
was still missing and the one guarantee automatic cleanup was missing.

Automatic cleanup no longer destroys a Session on the strength of a
cached snapshot. It asks the child to close only if unheld, and the
child answers under its own close gate — with the gate held no prompt is
admitted and no automatic turn starts, so a hold cannot appear between
the check and the teardown. A refusal hands back the current holds and
the daemon adopts them. An unanswered request is neither retried nor
assumed: the Session stays, and the next snapshot settles it, because a
Session absent from one has provably been released. Every automatic path
— detach, attach rollback, prompt settle, notification settle, a child
reporting itself idle — now funnels through one decision point instead
of four near-copies.

Health gains activeWorkReporting and activeWorkStaleMs. Without them
activeWork:false cannot be told apart from "no child told me anything",
which is the one case where acting on it is unsafe. Freshness is graded
by the daemon rather than the controller, since the cadence is negotiated
per channel; a stale snapshot or a child omitting a category degrades the
grade instead of silently narrowing what the boolean covers.

Tests: acp-bridge 489/489, acpAgent 383/383, Session 534/534,
serve suites 1188 with one pre-existing cross-file flake in the Live
Appshot integration tests (reproduces on the unmodified tree, failing a
different test each run).

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

* fix(serve): contain snapshot-collection failures, and repair two Session mocks

CI caught two things the local runs missed.

The reporter's snapshot construction was unguarded. Only the send was
wrapped, so a throw while collecting a Session's holds escaped through
setInterval and queueMicrotask as an uncaught exception — capable of
taking down the ACP child — and through flush() into the prompt path,
turning a reporting problem into a failed prompt. Collection is now
wrapped and a failed snapshot is abandoned whole rather than sent
partially: a Session missing from a report reads as released, and one
reported with no holds reads as safe to close, so publishing a partial
snapshot would actively invite the daemon to destroy live work. Sending
nothing lets the daemon's copy age instead, which its freshness grading
already treats as untrustworthy and retains. flush() no longer rejects.

Session.review-lease and Session.worktree mock the background-task
registry without setStatusChangeCallback, so constructing a Session threw.
That break arrived with the original commit, which verified only
Session.test.ts; the sibling Session.*.test.ts files were never run. Both
mocks now carry the methods the constructor and the hold collector need.

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

* test(serve): prove the reporter contains collection and transport failures

The previous commit added the guard but could not have demonstrated it:
the same commit also gave the acpAgent Session mock a
collectActiveWorkHolds, removing the very condition that triggered the
throw. The unhandled error disappearing was therefore explained by the
mock alone, and active-work-reporter.ts had no tests at all.

These cover the escape routes that matter — the interval timer, the
coalescing microtask, and flush() on the prompt path — plus the choice to
abandon a whole snapshot rather than send a partial one, since a session
omitted from a report reads as released and one reported with no holds
reads as safe to close.

Verified by removing the guard: five of the nine fail with the collection
error escaping, and pass again once it is restored.

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

* fix(serve): make every automatic teardown ask before destroying

Self-review of the previous revision found that this PR had promoted a
cached child report from a hint into the authority that permits destroying
a Session. Four teardown paths consulted it, their guards disagreed with
each other, and each was weaker than what main had. The four are one
defect with four exits, so they are fixed as one change.

Absence from a snapshot no longer authorizes teardown. Because reports
are complete, a Session the child omits holds nothing on the child side —
so absence and reported-with-no-holds are the same fact and now take the
same path. The separate absence loop is gone; it lacked the subscriber
and client guards `maybeCloseIdleSession` applies, so one snapshot could
destroy a Session with a live SSE subscriber and a registered client.
That contradicted this PR's own claim that an unreported Session is
retained, and the old test asserted the destruction. Both are corrected.

A conditional close is now marked in flight across the whole confirm-then-
teardown span, and attach, prompt, and rewind refuse a Session in that
state exactly as they refuse one already closing. `closeSessionImpl` sets
`closing` synchronously, but the round trip in front of it is an await of
up to ten seconds; on main the guard sequence ran straight into teardown,
so splitting it is what opened the window.

A snapshot older than the freshness window stops counting as evidence.
Staleness was already computed, but only to grade health, never to gate
destruction — so a child that went quiet after one empty report left a
cache that permitted reaping indefinitely. Never-reported and gone-quiet
now land in the same retained bucket. Reclaiming a channel that has truly
stopped answering belongs to transport liveness, not here.

The idle reaper asks the child too. Its TTL says the client stopped
caring, which is not the same as the child having nothing left to run.

Health coverage is exposed as counts and graded once daemon-wide, because
grades do not compose: a runtime with zero Sessions is vacuously `full`,
and folding that in let an empty workspace vouch for another workspace's
unreported Sessions. `activeWorkStaleMs` now measures only covered
Sessions, so it can no longer report positive staleness beside a grade
saying nothing is covered.

Also: bound snapshot `sessions[]` and `holds[]` so a buggy child cannot
make the daemon walk an unbounded structure per report, and retract the
background-task status callback by identity rather than blanking a
single-slot setter the TUI also uses.

Tests: the absence test now asserts retention under a registered client
and under a live subscriber; new regressions cover the recovered lost
close response, the stale-snapshot gate, admission refusal during a
conditional close, the reaper's confirmation, the oversized-snapshot
discard, and the mixed empty/uncovered health aggregate.

* fix(serve): make unknown a reason to ask, not a reason to skip

Triage review found that the design doc, the PR description, and the
comment on `entryHasActiveWork` all promised the daemon *asks* the child
about a Session it has not heard about, while no code path ever did:
`entryHasActiveWork` returns true when the child's side is unknown, and
the cleanup path returned early on exactly that. The finding predates the
guard rework and survived it unchanged.

Skipping on unknown looks like the safe direction and is in fact the worse
failure. Nothing resolves it — a Session on a channel that went quiet is
retained forever, and the idle reaper skips it too, so there is no path
out at all. Asking resolves it definitively: the child answers under its
own close gate whether or not its snapshots are arriving, the round trip
is bounded, and every non-answer still retains.

So the predicate is split by what it actually knows. `childReportsHeldWork`
is positive knowledge only; `childWorkIsUnknown` is the absence of a
gradeable report. The health surface ORs both, because a controller must
never read "nobody told me" as "nothing is running". Automatic cleanup
blocks only on known work and lets unknown through to `confirmChildUnheld`.

Also moves `parseActiveWorkSnapshot` out from between two import blocks
(pure relocation, no logic change) and aligns the doc wording, including
the shared-guard table, with what the code now does.

* fix(serve): close three teardown races the confirm window opened

Review found three ways the conditional close can still destroy a live
Session. All three share a cause: the round trip turned a synchronous
guard-then-teardown into an awaited span, and three things that were
previously impossible to observe mid-teardown now are.

**A restore in flight looks exactly like an abandoned Session.**
`session/load` registers the entry before awaiting `artifacts.restore()`
and `seedSessionUpdates()`, and registers its first client only after —
so for that whole window there are no clients, no subscribers, nothing
held, and the child answers the conditional close truthfully. The
snapshot trigger this PR added fires inside it. Excluded in
`entryIsAutoCloseCandidate` rather than at the snapshot trigger, so the
reaper's TTL elapsing inside a slow restore is covered too.
`pendingRestoreIds` already existed but was read only by
`hasNoChannelWork`, never by the close funnel.

**Teardown re-resolved the target by id without re-checking identity.**
`closeSessionImpl` does a fresh `byId.get`, and the id can be
re-registered to a different entry during the round trip: an explicit
kill removes this one (kill ignores the in-flight flag by design, keeping
its force semantics) and a `session/load` for the same persisted id
registers a fresh one. The stale continuation then tore down the newly
restored Session under its just-attached client. One identity re-check
after the await.

**The restore path was not upgraded to the new admission predicate.**
`sendPrompt`, `rewindSession`, and single-scope attach check
`isClosingOrAuthorizingClose`; `restoreSession` still checked bare
`closing` at both its guards, so a client could attach inside the window
and lose the session under it. That directly contradicted the
`closeIfChildUnheld` comment claiming every admission path checks the
flag. Its `racedEntry` branch had no closing guard at all — a narrower
pre-existing hole, same defect, same predicate.

Regression test covers the restore-path admission refusal. The other two
need a mid-restore snapshot and a kill-then-reload interleave that the
mocked-channel harness cannot stage honestly; both are pinned by reading
the code paths, which is weaker and worth saying.

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 06:34:20 +00:00
C0d3N1nja97342
fd76d4ddde
fix(cli): let ESC cancel ongoing work before popping queued messages (#8353)
* fix(cli): let ESC cancel ongoing work before popping queued messages

When the agent is actively responding (streamingState === Responding),
InputPrompt's ESC handler consumed the key before AppContainer's global
cancel-work handler could fire. Users had to press ESC 3 times (pop queue,
clear input, cancel work) to stop the agent.

Skip the pop-queue-into-input and double-ESC-clear logic when the agent
is responding, returning false so the key propagates to the global
handler which cancels the ongoing request. The up-arrow key still pops
queued messages into the input at any time.

Fixes #8201

* fix: narrow ESC fall-through to empty buffer + add regression tests

Address wenshao's review on #8353:

- Gate the return false on buffer.text === '' to prevent BaseTextInput's
  default ESC from silently wiping typed input without double-press
  confirmation
- Add resetEscapeState() before return false to clear any pending
  escPressCount/escape-prompt timer
- Add two regression tests with streamingState: StreamingState.Responding:
  1. queue non-empty + ESC -> popAllQueuedMessages NOT called
  2. buffer has text + single ESC -> buffer NOT cleared

* fix: correct ESC comment to reflect KeypressContext broadcast model

Address bot suggestion: the comment claimed returning true 'consumed the
key before the global cancel-work handler could fire', but KeypressContext
broadcasts to all handlers regardless of return value. The real mechanism
is that popQueueIntoInput() fills the shared buffer, steering AppContainer's
handler into its 'input has content -> double-press to clear' branch
instead of the cancel-work branch.

* fix: correct comment to accurately describe return false -> BaseTextInput fall-through

Bot suggestion: the comment said returning false 'avoids' BaseTextInput's
wipe, but return false actually *enables* it (BaseTextInput only
short-circuits on truthy returns). The buffer is safe because the
buffer.text === '' gate makes the wipe a no-op, not because return false
prevents it. Reworded to make this explicit and warn against relaxing
the gate.

* test(ui): add positive AppContainer ESC cancel regression test

The PR's Responding guards were pinned only by InputPrompt-side tests
(queue not popped; non-empty buffer preserved). Add the positive case the
review asked for: while Responding with an empty buffer and queued
follow-ups, a single Esc reaches the global handler's cancel-work branch
(cancelOngoingRequest called once) and the queue is not consumed. #8201

* test(ui): clarify ESC cancel test scope vs end-to-end drain

The positive ESC cancel test asserts popAllMessages is not called, but
the comment framed it as 'must not consume the queue' end-to-end. In
production that exact cancel path DOES drain the queue back into the
buffer via the cancel handler (cancelOngoingRequest -> onCancelSubmit ->
popAllMessages), under the 'never silently drop queued work' invariant.
The assertion only holds because cancelOngoingRequest is replaced by a
spy here, severing that hop. Reword the comment to describe the real
contract: the global keypress handler itself doesn't pop the queue
(InputPrompt owns that and skips it while Responding; #8201), while the
end-to-end drain is a separate hop severed by the spy.

Addresses the review finding on AppContainer.test.tsx:2405.

* test(ui): pin ESC return-false branch and dedupe getGlobalKeypress

Address two review suggestions on the ESC cancel tests:

- The `return false` branch in InputPrompt.tsx (Responding + empty
  buffer + empty queue -> defer to AppContainer's cancel-work branch)
  had no test coverage: reverting it left all 334 tests green. Add an
  InputPrompt test that pins it (no queue pop, no buffer mutation).
- The AppContainer cancel test inlined a byte-identical copy of the
  getGlobalKeypress() helper that already existed ~2600 lines down in
  the Ctrl+O describe block. Hoist the helper to the outer describe so
  both blocks share one definition of the fragile toString() discovery
  idiom.

* test(ui): escape raw ESC byte in cancel test fixture

Per review (R4-1): the sequence literal embedded a raw 0x1B control
byte that renders as an empty string in diffs and truncates grep output,
so the fixture was unreadable. Use the escaped form matching the
sibling vim-INSERT fixture on the line above.

* test(ui): assert buffer stays empty on ESC cancel + fix test comment

Per review (R5-1/R5-2): the flagship #8201 test asserted only the
mechanism (popAllQueuedMessages not called), not the effect (buffer
stays empty so AppContainer takes its cancel branch). Add the buffer
assertion. Also correct the return-false test comment: deleting that
branch leaves the test green (KeypressContext.broadcast ignores return
values, BaseTextInput's clear is a no-op on empty buffer), so the test
pins the no-side-effect contract, not the branch itself.

* test(ui): cover queue+text ESC and double-ESC-clear while responding

Per review (R6-1/R6-2): the pop-skip guard was pinned only for the
empty-buffer case, and the double-press clear contract had no test
under Responding. Add:
- non-empty queue AND typed text: ESC does not pop the queue and
  preserves the buffer (pins the guard regardless of buffer content).
- double-ESC while Responding: first ESC preserves typed text, second
  clears it (pins the double-press contract this diff preserves).

* test(ui): dedupe escKey fixture and tighten double-ESC timing

Per review (R7-1/R7-2): the double-ESC test spaced presses with the
default 150ms wait (~30% of the 500ms window); use 50ms to match the
sibling double-ESC test. Hoist the escKey fixture to the Cancel
Handler describe scope so both tests share one definition (matching
the getGlobalKeypress hoist this PR already did).

* test(ui): add missing removeGoalTurns to cancel-handler queue mock

Per review: the cancel-handler test's useMessageQueue mock omitted
removeGoalTurns, a required member that every other queue-mock override
in this file includes. The real cancel handler calls removeGoalTurns()
before popAllMessages(); the test passed only because cancelOngoingRequest
was a spy severing that hop.

* test(ui): reuse getGlobalKeypress in vim-INSERT cancel test

Per review (R9-1): the vim-INSERT test still inlined a handler-discovery
loop matching on 'handleExit', duplicating the hoisted getGlobalKeypress
helper (matching TOGGLE_THINKING_EXPANDED). Both tokens occur in the
same handleGlobalKeypress closure, so the two idioms can only drift.
Reuse the shared helper.

* test+docs(ui): escape ESC byte in shared fixture and document Responding Esc

Per review (R10-1/R10-2): the shared escKey fixture embedded a raw
0x1B control byte (invisible in diffs, truncates grep). Use the
escaped form. Also update keyboard-shortcuts.md: Esc now cancels the
ongoing request while the agent is responding instead of moving
queued messages back into the input.

* docs(ui): correct ESC/Up-Arrow queue-pop description to match code

Per review (R11-1): the previous wording said queue pop happens only
when idle, but Up Arrow pops in any state (no streamingState guard)
and Esc pops whenever not actively responding (including
WaitingForConfirmation). Reword to match the code, and note that the
responding-cancel only fires when the input is empty.

* refactor(ui): drop dead Responding ESC guard per maintainer review

wenshao's mutation test showed guard #2 (Responding + empty buffer ->
return false) is dead code: with it gone, control falls to the
escPressCount===0 branch which returns true on an empty buffer, and
KeypressContext.broadcast ignores handler return values anyway -
AppContainer's own cancel branch acts on the empty buffer either way.

Remove it, fold the subscription-ordering invariants it relied on into
guard #1's comment, and document that only Responding is gated (do not
broaden to !== Idle or ESC becomes a no-op during a tool confirmation).
Also tighten the docs wording: cancelled queued messages are moved back
into the input, not preserved. #8201

* docs(ui): correct four review comments in ESC cancel path

Round-13 review (no blockers) flagged comment inaccuracies that could
misdirect future debugging:

- R13-1: the invariant comment pointed at an integration test that does
  not exist - note the harnesses mock each other's side instead.
- R13-2: the regression-test comment still said the branch returns false
  and that AppContainer acts on return values; it returns true and
  broadcast ignores return values.
- R13-4: the docs row claimed Up Arrow/Esc pop in any state, but during
  WaitingForConfirmation Composer unmounts InputPrompt (isInputActive
  admits only Idle/Responding), so neither key pops.
- R13-5: the guard comment warned against broadening to !== Idle as if
  WFC ran this branch; it never does because the component is unmounted.

No behavior change. #8201

* docs(ui): correct subscription-order and double-ESC-cancel comments

Round-14 review: the invariant comment overstated subscription order as
load-bearing - the Responding pop guard skips the pop in either order,
and InputPrompt re-subscribes after AppContainer on any remount (e.g. a
tool-confirmation round trip), so only the buffer.text-liveness invariant
matters. And the double-ESC clear comment now notes it composes with
AppContainer's cancel on the same keypress in the initial order but lands
on the next press after a remount. No behavior change. #8201

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-08 05:14:42 +00:00
qqqys
88a325bce9
feat(workflows): add cooperative pause and resume (#8320)
* feat(workflows): add cooperative pause and resume

* fix(workflows): restrict pause to background runs

* fix(cli): clarify foreground workflow pause errors

* fix(core): preserve dispatch errors across cancellation

* test(core): cover late workflow state callbacks

* fix(workflows): address review suggestions (#8320)

- Rename misleading `terminal` local to `presentation` in BackgroundTasksDialog
- Fix vacuous `toContain('p')` assertion to `toContain('Background tasks + p')`
- Fix vacuous gate assertion with macrotask yield in scheduler test
- Add over-count cap test for `onAgentCompleted` past dispatched count
- Add pausing-state approval parking test
- Remove dead `concurrencyLimiter` module (no production consumers)

* test(workflows): pin review-flagged mutation-surviving branches (#8320)

* test(cli): use valid agent status in detail-view reset test (#8320)

* test(workflows): harden pause-gate settle probes with a full flush (#8320)

* fix(workflows): address round-5 review findings (#8320)

* test(ci): sync review timeout assertions with repository variables (#8320)

* fix(workflows): address round-6 review findings (#8320)

* fix(workflows): address round-7 review findings (#8320)

* fix(workflows): address round-8 review findings (#8320)

* fix(workflows): address round-9 review findings (#8320)

* fix(workflows): address round-10 review findings (#8320)

* fix(workflows): address round-11 review findings (#8320)

* fix(workflows): address round-12 review findings (#8320)

* fix(workflows): address round-13 review findings (#8320)

* fix(workflows): address round-14 review findings (#8320)

* fix(workflows): address round-15 review findings (#8320)

---------

Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-08 04:21:21 +00:00
BaboBen
d3164572f3
feat(channels): add Feishu ask-user question cards (#8578)
* feat(channels): add Feishu ask-user question cards

* fix(channels): harden Feishu question-card handoff and feedback paths

* fix(channels): anchor Feishu card-creation timeout at creation start (#8578)

* fix(channels): skip settled question-card delivery, retry input-request finalization

- present() returns early when the request settled before listener
  registration (stop/cancel landing in the pre-presentation await):
  the card is no longer delivered just to be patched terminal, which
  left a spurious actionable-looking card when the patch failed.
- endOutputCardBeforeInputRequest now retries the final patch with
  stripTables before deleting the card and re-sending plain text,
  mirroring onResponseComplete and the throttled update path.
- truncateCardText budgets the 4-char fence prepend so rebalanced
  content stays within MAX_CARD_CHARS, matching onResponseComplete.

* test(channels): cover Feishu question-card review findings

- stop real network leaks: mock addReaction/removeReaction in the two
  handoff tests that drove onPromptStart with the real implementation
  (each run hit Feishu's tenant-token endpoint).
- assert the OnIt reaction add/remove lifecycle positively.
- pin settled-before-registration presentation to zero card delivery.
- regression tests: input-request table-stripped retry, finalizing
  race (throttled timer cleared before the final patch), late card
  deletion when the input request lands mid-creation, abandoned
  creation-timer guard.
- routing coverage for toast-only handled callbacks (no execute).
- parse rejections: empty/malformed multi-select values; cancel-side
  name<->operation_id mismatch; visible submit/cancel button labels;
  cancelRun terminal label; submitted answers in patch-failure
  fallback; truncation tail-keeping and fence-rebalance cap.
- harden subset toMatchObject assertions to strict toEqual.

* fix(channels): type Feishu card-delivery errors; cover controller wiring seam

sendInteractiveCard now throws FeishuCardDeliveryError (with the HTTP
status when available) so createStreamingCard classifies delivery
failures by error type instead of string-matching three message
literals that could drift under rewording.

Add an adapter test that keeps the real FeishuQuestionCardController
with fetch mocked, asserting the POSTed body carries the question-card
JSON (form name, question text, request id) — the constructor wiring
seam every routing test previously mocked away.

* fix(channels): cap Feishu input-request card retry; harden question-card tests

* fix(channels): drain in-flight Feishu card PATCHes before final patch (#8578)

Also carry a concurrently-written terminal status into the released
card entry, report card-delivery failures from a typed error detail
instead of slicing the message, and document the silent
empty-completed-turn tradeoff. Adds round-5 review test pins: wiring
seams (sendFallback, patchCard, timeoutMs), spy hygiene, claim-time
card content, fallback suppression, expiry-timer survival, and
non-vacuous release-time timestamp anchoring.

* fix(channels): coordinate Feishu card stop races and throttle bursts (#8578)

- endOutputCardBeforeInputRequest now defers to Stop (entry guard plus
  post-await re-checks mirroring onResponseComplete) and releaseOutputCard
  carries settled stop state, so a stopped run is never labelled 已完成 nor
  followed by a contradictory terminal message
- coalesce throttled card updates queued behind a stalled PATCH into one
  trailing run instead of a burst
- log every FeishuCardDeliveryError from createStreamingCard
- consolidate onResponseComplete truncation through truncateCardText
- question controller: a not-accepted in-flight response no longer flips a
  projected 已取消 card to 已过期, and a cancel claim no longer re-patches
  the terminal card already delivered in the callback response
- test hygiene: hermetic fetch mocks for token-failure tests, restored
  fetch spies, required lifecycle fixture fields, bracket index access

* test(web-shell): wait for history search focus before Escape in smoke (#8578)

* fix(channels): resolve Feishu card finalization races from review (#8578)

* fix(channels): recover Feishu cancel callbacks missing button value (#8578)

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-08 04:20:17 +00:00
callmeYe
52f4fbe0b3
feat(web-shell): install Extensions from archives (#8621)
* feat(web-shell): install extensions from archives

* fix(web-shell): harden extension archive uploads

* test(cli): align archive failure bridge assertion

* fix(extensions): address archive upload review feedback

* fix(webui): stagger extension archive upload timeout

* fix(extensions): address archive upload round-4 review feedback

* fix(extensions): address archive upload round-5 review feedback

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-08 02:08:52 +00:00
destire-mio
b34a08d16f
fix(core): separate hook context from transcript display (#7948)
* fix(core): separate hook context from transcript display

* test(ci): gate desktop transcript projection

* revert: keep desktop CI scope unchanged

* test: cover transcript display fallbacks

* fix(transcript): address review feedback

* fix(transcript): reconcile post-merge provenance paths

* fix(webui): preserve legacy transcript concatenation

* test(transcript): cover projection consumers

* fix(transcript): consolidate hook context projection

* fix(transcript): support single-field display provenance

* fix(transcript): strip hook context with invalid metadata

* test(acp): cover empty replay display text

---------

Co-authored-by: destire-mio <248462155+destire-mio@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-08 02:03:20 +00:00
Dragon
681e30d54f
docs: clarify SDK interrupt behavior (#8711) 2026-08-08 01:48:16 +00:00
Shaojin Wen
92ff0a1363
perf(review): move remote matching into CLI (#8658)
* perf(review): move remote matching into CLI

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

* fix(cli): use exit 7 for match-remote multi-match, drop dead field (#8658)

* fix(cli): harden match-remote host resolution per review feedback (#8658)

- Strip an explicit port from the input host before remote comparison;
  a port-bearing GHE verdict host could never match its own remote and
  a same-repo review was demoted to lightweight mode
- Exit 1 only when git itself fails: a bare repository now resolves
  remotes like any other checkout, matching the documented contract
- Inherit an operator-exported GH_HOST when --host is absent, the same
  resolution submit uses, so bare PR numbers on GHE clones match
- Write the machine-read stdout line with loud writeStdoutLine so a
  failed write exits non-zero instead of exiting 0 with empty output
- chdir out of the temp dir before rmSync in match-remote.test.ts
  (Windows locks a directory that is the process cwd)
- Pin both SKILL.md match-remote hunks in SKILL.test.ts so reverting
  either path to model-prose matching fails a test
- Correct the design doc's prompt-size accounting and host-resolution
  paragraph; label the e2e pointer as an untracked run archive

* fix(cli): match partial-clone remotes and unify host resolution (#8658)

* fix(cli): thread resolved GHE host through bare-number remote matching (#8658)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-08 01:47:32 +00:00
Nothing Chan
0701b76b87
fix(core): refresh MCP session metadata without reconnecting (#8522)
* fix(core): refresh MCP session metadata in place

* fix(core): isolate MCP metadata refresh

* fix(core): harden MCP session metadata key and refresh (#8522)

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@service.alibaba.com>
2026-08-08 01:33:41 +00:00
Nothing Chan
c2026882b7
fix(acp): emit context usage updates (#8513) (#8528)
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-08 01:09:51 +00:00
易良
4ec0371e61
feat(telemetry): attribute daemon-spawned sessions via channel (daemon/desktop) (#8670)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(telemetry): add runtime and client attribution to usage statistics

Daemon-spawned sessions (TS/Python/Java SDKs, Web Shell, Tauri desktop
shell) all report properties.channel=ACP because the daemon spawns plain
`qwen --acp` children with no channel argument.

Add two stable dimensions to the default usage-statistics payload:

- properties.runtime (cli|acp|daemon): the daemon marks every child it
  spawns with QWEN_CODE_SERVE=1 (ACP session children and channel
  workers), which takes precedence over the channel-based acp heuristic.
- properties.client (vscode|desktop|desktop-shell, omitted when unknown):
  derived from the --channel value (VSCode/desktop) and the
  QWEN_CODE_DESKTOP env marker set by the Tauri desktop shell.

properties.channel and app.channel are unchanged; the new keys are purely
additive. See docs/design/telemetry-runtime-client-attribution-design.md.

Issue: #8660

* chore(telemetry): use Qwen Team copyright header on new attribution files

New files use "Copyright 2026 Qwen Team" per repository convention
(see e.g. packages/core/src/utils/file-identity.ts), not the legacy
Google LLC header inherited from the gemini-cli fork.

* refactor(telemetry): report daemon attribution via channel, no new payload keys

Per maintainer feedback: extend properties.channel instead of adding
properties.runtime/client. getChannel() has no behavioral consumers
(telemetry is its only reader), so channel is a pure reporting dimension.

- resolve the ACP channel fallback from the daemon env markers:
  QWEN_CODE_DESKTOP -> desktop-shell, QWEN_CODE_SERVE -> daemon, else ACP
- drop the runtime/client payload keys and the runtime-attribution module
- keep the QWEN_CODE_SERVE spawn marker at both daemon spawn sites

* refactor(cli): report Tauri desktop-shell sessions as channel=desktop

Maintainer decision: the Tauri desktop shell shares the desktop client
identity with the Electron app instead of getting a separate
desktop-shell value. Also document the SDK split: SDK query() spawns
the CLI directly with --channel=SDK (never daemon, unchanged), while
the SDK daemon-client entrypoints ride the daemon bridge and report
channel=daemon.

* fix(cli): preserve daemon channel attribution

* fix(cli): keep attribution markers out of home env bootstrap
2026-08-07 18:13:03 +00:00
jinye
26352fcc6a
feat(external-context): Add optional Mem0 memory writes (#8507)
* feat(external-context): Add optional Mem0 memory writes

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

* fix(hooks): Preserve confirmation content visibility

Render PreToolUse confirmation reasons literally and keep long confirmations accessible through the virtualized TUI. Add unit and interactive regression coverage for Mem0 write confirmations.

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

* fix(external-context): Address memory write review findings

Align Hook and MCP argument handling, distinguish definitive Provider rejections from ambiguous outcomes, improve deployment diagnostics, and document the write-back trust boundary.

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

* fix(hooks): Refine plain-text confirmations

Render URLs consistently, avoid persistent virtual viewport gaps, and document the literal-rendering and managed deployment boundaries.

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

* fix(external-context): Support Auto Edit write confirmation

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

* fix(external-context): Harden write confirmations

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

* fix(cli): Measure virtual row height directly

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

* fix(cli): Preserve YOLO Hook confirmation content

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-07 16:52:33 +00:00
BaboBen
028747aa41
feat(feishu): enrich observed contact labels (#8569)
* docs: design feishu observed contact enrichment

* docs: add Chinese Feishu enrichment design

* feat(feishu): enrich observed contact labels

* fix(feishu): preserve enriched contact labels

* fix(feishu): harden observed-contact label enrichment lifecycle

* fix(feishu): bound label caches, honor observation recency, silence enrichment token failures (#8569)

- hydrate runtime label caches from the newest observation per contact so
  stale group membership labels cannot overwrite more recent ones
- cap the user/chat label, in-flight lookup, and write-dedup maps at 500
  entries (matching the persisted registry) and evict oldest entries
- route best-effort label lookups through a silent token refresh path so
  enrichment failures no longer write to stderr
- add tests for silent token refresh, newest-label hydration, cache cap,
  and the persisted-observation reject path in hook ordering

* fix(feishu): address observed-contact label review feedback (#8569)

* Track core (non-silent) waiters on the shared tenant-token refresh so a
  silent-initiated refresh still logs token errors for joined delivery
  callers.
* Short-circuit label lookups on the resolved names cache so evicted
  lookup entries do not trigger redundant API requests.
* Re-hydrate label caches from the persisted registry after an
  in-lifetime cache eviction so the next initial write cannot clobber a
  persisted label with the raw ID.
* Add mutation-proof regression tests for the channel-isolation filter,
  the list-failure swallow, the silent HTTP-error branch, and the
  'unknown' label guard.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
2026-08-07 16:41:37 +00:00
Shaojin Wen
b40719a8bb
fix(cli): Run ACP agent fan-outs concurrently and past the tool-call cap (#8631)
* fix(cli): Run ACP agent fan-outs concurrently and past the tool-call cap

The daemon's ACP session executed tool batches differently from the
core scheduler in two ways that broke long agent fan-outs such as
/review:

runBounded — the runner for concurrent batches — forced the first
three calls of any batch larger than the invalid-params threshold to
run one at a time, then clamped the rest to concurrency 3, although
agent calls are concurrency-safe and core's runConcurrently runs them
at QWEN_CODE_MAX_TOOL_CONCURRENCY (default 10). A /review fan-out of
10-15 agents therefore ran almost serially. Agent-only batches now
skip the serial prefix and the clamp: an invalid agent call fails in
build() before any side effect, so the concurrent loop's
near-threshold check still catches invalid-params loops just as fast.

The per-turn tool-call cap halted unconditionally at
model.maxToolCallsPerTurn (default 100) while core's
LoopDetectionService treats the default as adaptive — past the soft
cap a productive turn (diverse calls, no repetition) continues until
a stuck-repetition signal or the hard backstop (soft cap x 10). A
/review orchestrator needs well over 100 calls, so every high-effort
review under qwen serve died mid-review at call 101. The daemon now
mirrors core's checkTurnToolCallCap semantics, reusing the same
thresholds.

Measured on two high-effort /review runs (PRs #8522 and #8529): the
baseline died at call 101 after ~8.3h each; after this fix both
reviews run to completion in 4.4h / 5.3h, first fan-out wave 85m ->
33m, reverse-audit rounds 63-86m -> 25-35m.

* fix(cli): regenerate settings schema after maxToolCallsPerTurn doc update (#8631)

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

* fix(cli): Gate the daemon repeat halt on skipLoopDetection like core

* fix(cli): Address ACP fan-out review: keep wide-batch results, shared cap predicate (#8631)

- runBounded no longer aborts in-flight calls when loop detection fires
  in the capped race branch: wide batches keep in-flight results and only
  skip the unstarted tail, matching narrow-batch behaviour (nothing
  executed is discarded either way).
- Extract shouldHaltOnTurnToolCallCap from core's checkTurnToolCallCap
  and call it from the daemon guard so the two runtimes share one halt
  predicate and cannot drift.
- Hoist canonicalToolName into tools/tool-names.ts beside
  ToolNamesMigration; scheduler, loop detection, plan redaction and
  memory refresh now share the single alias resolver.
- Correct the wrong-direction cap wording (the daemon undershoots an
  explicit cap / hard backstop — the batch check runs before execution;
  the adaptive soft cap is exceeded by design up to the backstop) in the
  daemon comment, settingsSchema.ts (schema regenerated) and settings.md,
  and scope the always-on-guard sentence to core-client sessions.
- Tests: adaptive hard backstop, wide-batch loop tail skip, wide-batch
  keep-results, provider-duplicate counter exclusion, `task`-alias
  fan-out, getToolCallRepeatKey alias/key-order coverage; raise the
  fan-out concurrency deadline off the 2s wall clock.

* fix(cli): Address review: complete loop-guard docs, pin test envs, drop dead export (#8631)

* fix(cli): Address review: correct parity comment, pin halt semantics, test cross-response repeats (#8631)

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-07 16:32:44 +00:00
BaboBen
edb420393e
fix(channels): manage DingTalk interactive card config (#8517)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* fix(channels): manage DingTalk interactive card config

* test(cli): cover nested channel object validation

* fix(channels): harden nested management metadata

* fix(channels): isolate invalid management descriptors

* fix(channels): isolate invalid management metadata from channel runtime

* fix(channels): reject reserved unknown keys in management config upserts

* fix(channels): harden channel management validation and editor checks

Reject management descriptors that lack a fields array at registration so
broken plugins are stripped to unmanageable instead of being advertised as
manageable and failing every upsert with an unmapped TypeError. Reserve the
top-level "type" field key and require enum fields to declare at least one
option, both of which the settings store could never accept. Treat
whitespace-only number drafts as empty in the channel editor, consistent
with the module's other emptiness checks.

Also give the SDK descriptor mirror test a runtime wire-shape walk over the
built-in catalog, add the parser's timeout rejection boundary, and restore
the exact built-in catalog membership assertion.

* fix(channels): validate management field shapes and editor bounds (#8517)

* fix(channels): align management validation layers and pin gate behavior (#8517)

Read envResolvable by truthiness in the settings store so it matches the
registration gate and the editor, instead of rejecting the advertised
environment references of untyped plugins. Fail closed at registration on
non-finite exclusiveMinimum values, empty object property lists, and async
validateConfig functions, all of which would otherwise advertise a field
or save path that can never succeed. Strip invalid management metadata
over a prototype-preserving copy so class-instance plugins keep their
createChannel implementation.

Move the unchanged-value preservation exemption ahead of the object shape
rejection so a stored non-record value (for example a hand-written null)
no longer locks every unrelated management edit of that channel. Clamp
DingTalk question-card timeouts at the maximum setTimeout delay, since
Node treats larger delays as one millisecond and would expire cards
instantly.

Pin the previously untested load-bearing behaviors: per-key previous
threading in the recursive validation, the preservation exemption's
precedence over nested required enforcement, nested "type" properties,
depth-2 nesting rules, and the nested-only constraints of the daemon
descriptor wire contract.

* fix(channels): close reserved-key preservation gaps and pin gate behavior (#8517)

* test(cli): tolerate IPv6-less hosts in serve ::1 bind tests (#8517)

The self-hosted CI containers can have no IPv6 loopback, where the two
runQwenServe tests that bind ::1 fail with EADDRNOTAVAIL. Probe the
interfaces once and skip only the IPv6-dependent binds there; every
assertion still runs on IPv6-capable hosts.

* fix(channels): align descriptor type contracts with runtime validation (#8517)

The registry already rejects object fields without a non-empty
properties array and enums without unique options, but the descriptor
types still admitted both, so TS-authored plugins only learned about
it when registration stripped their management surface. Make
`properties` required, give enums a dedicated descriptor member with
required `options`, and drop the never-honored `envResolvable` flag
from number descriptors, in both channel-base and the SDK mirror, and
export the descriptor sub-types through the webui barrels. Also map a
throwing `validateConfig` to the usual invalid-config error and pin
the store contracts that had no distinguishing tests: omitting a
parent object drops the stored object without checking its nested
required, writes replace nested values wholesale, unchanged stored
scalars are still re-validated, and valid plugins register by
original reference.

* fix(channels): defuse validateConfig rejection leak and close descriptor gate gaps (#8517)

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

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-07 15:47:20 +00:00
qqqys
3edecac116
feat(channels): support group pairing (#8440)
* feat(channels): support group pairing

* fix(channels): address group pairing review

* fix(web-shell): show group pairing management

* fix(channels): recheck group pairing before history backfill

* test(channels): verify group approval isolation

* fix(channels): address group pairing review findings and pin behaviors (#8440)

- Grandfather the group allowlist file in PairingStore legacy migration
- Offer the pairing groupPolicy option in github/gitlab descriptors
- Re-export DaemonChannelPairingSubject from the webui barrels
- Refresh the GroupGate doc comment and channel docs rows
- Pin the unpinned group pairing behaviors called out in review:
  subject dedup, trigger matrix, notification content/cap/failure/
  thread routing, DM negative space under groupPolicy pairing,
  stored DM loop authz, pairing-enabled guard negative space,
  approval/revocation HTTP bodies, descriptor-driven gate branch,
  and the web-shell group approval mirrors
- Add a compile-time assertion for the revocation request union

* fix(channels): address group pairing review findings (#8440)

- Accept 'pairing' in the GitLab connect warning, descriptor help text,
  and gitlab.md: todos dispatch after one-time group approval.
- Model group approvals in the web-shell e2e mock daemon (approve by
  subject type, GET returns senderIds+groupIds, DELETE accepts groupId)
  and exercise the group pairing flow in the channels spec.
- Add 'pairing' to the groupPolicy enumerations in the plugins and
  per-channel docs (telegram, feishu, dingtalk, qqbot, wecom).
- Update the channel pairing CLI help to cover group requests.
- Cap pending pairing requests at one per sender so a single member
  cannot occupy every shared pending slot.

* fix(channels): address group pairing review findings round 7 (#8440)

* fix(channels): address group pairing review findings round 8 (#8440)

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

* fix(channels): address group pairing review findings round 9 (#8440)

---------

Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-07 09:20:18 +00:00
Dragon
adec1ea50f
feat(core): share compression cache with Gemini and Vertex AI (#8425)
* feat(core): share compression cache with Google GenAI

* fix(core): preserve restored compression accounting

* fix(core): preserve estimated compression accounting

* fix(core): preserve estimated token provenance on resume

* fix(core): harden compression provenance flow

* fix(core): keep compression counts conservative

* fix: preserve compression token provenance

* fix(web-shell): preserve estimated context usage

* fix(core): require provider-reported anchor for compression cache sharing

An estimate-derived token count misses the ~15-20K system/tools overhead
the shared compression request carries, so a magnitude-only anchor gate
could approve a shared request that overflows the context window. Gate
cache sharing on a provider-reported count, keeping estimate-only
sessions on the cold path until provider usage arrives.

Pin the zero-baseline end-to-end composition (derived baseline reaches
the service, missing anchor routes to the cold side query), repair the
garbled R3.4 test rationale comment, and log estimate-clamp padding.
2026-08-07 08:54:49 +00:00
jinye
bf3abdee81
fix(serve): Allow approved same-host text reads outside workspace (#8620)
Some checks failed
npm cache producer / Save npm cache (push) Has been cancelled
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* fix(serve): allow same-host daemon text reads

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

* docs(serve): address review on same-host text reads

Record what the read capability does not fix: #8618 still reproduces for
the write and edit family, whose delegated writes are refused after the
user has already approved the diff. Give the daemon's pre-approval SSE
fan-out its own bullet in the user-facing security section, restore the
sentence stating that environment isolation is not an OS security
boundary, and make the design doc the single owner of the tradeoff list
so tuning a limit cannot leave stale copies behind.

Test fixtures no longer land in the developer's real home directory, the
assertion pinned to localized rejection copy is dropped, and the combined
capability case is split so deleting the write half cannot silently
remove read coverage.

* fix(test): declare REPO_ROOT and bind the external-read session to the daemon's workspace

The external-read regression test referenced REPO_ROOT twice without
declaring it, which made it unrunnable everywhere:

- On a developer box the ReferenceError was swallowed by the bare catch
  in findExternalReadBase(), every candidate was discarded, and the test
  reported a green skip -- exactly the silently-disabled security test
  the CI loud-fail added last round was meant to prevent. The guard was
  defeated three lines above itself.
- On CI that loud-fail branch threw at module scope, so the file failed
  to collect and took the four pre-existing tests down with it.

Declare REPO_ROOT the way every other daemon integration test does.

The session also asked for `workspaceCwd: REPO_ROOT` while beforeAll
binds the daemon with `--workspace workspaceDir`, so the create returned
400 Workspace mismatch even once the constant existed. The read under
test is external because externalReadDir sits outside the bound
workspace, not because the session claims a wider one.

Finally, collect each candidate's rejection reason instead of dropping
it, and fold it into both branches: the CI throw names why every
candidate failed and the developer-box skip warns with the same text.
A bare catch cannot tell "no /var/tmp on this image" from a bug in the
function, and the second reads as a green skip.

Reported by @wenshao, who reproduced all three consequences against a
real qwen serve daemon on Linux and supplied the repair.

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 07:33:16 +00:00