qwen-code/docs/developers
Shaojin Wen b314d01f2d
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 (#8812)
* 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
..
daemon fix(serve): Make session restore timeouts safe and observable (#8691) 2026-08-09 15:47:09 +00:00
daemon-client-adapters docs(serve): Close multi-workspace hardening gaps (#7019) 2026-07-16 17:33:53 +00:00
daemon-ui fix(web-shell): stop rendering unrecognized daemon events in transcripts (#8812) 2026-08-10 00:51:57 +00:00
development feat(telemetry): Track tool execution outcomes (#8180) 2026-08-03 10:21:44 +00:00
examples docs(serve): Close multi-workspace hardening gaps (#7019) 2026-07-16 17:33:53 +00:00
tools fix(core): Avoid replaying unsafe MCP tool calls (#8387) 2026-08-03 11:04:38 +00:00
_meta.ts docs(daemon): Refresh daemon docs in English (#5144) 2026-06-15 22:38:01 +08:00
architecture.md docs: refresh architecture overview (#8325) 2026-08-02 02:45:00 +00:00
channel-plugins.md docs(channels): Document loops and proactive delivery (#7628) 2026-07-26 07:32:18 +00:00
contributing.md revert: remove local PR verification gate (#7031) 2026-07-16 11:24:38 +00:00
qwen-serve-protocol.md fix(serve): Make session restore timeouts safe and observable (#8691) 2026-08-09 15:47:09 +00:00
roadmap.md docs: standardize GitHub Actions capitalization (#6367) 2026-07-06 06:55:07 +00:00
sdk-java.md fix(serve): Coordinate caller-supplied session IDs (#8415) 2026-08-09 07:31:30 +00:00
sdk-python.md doc[sdk-python] Expand Python SDK usage documentation (#3995) 2026-05-12 15:27:00 +08:00
sdk-typescript.md fix(serve): Coordinate caller-supplied session IDs (#8415) 2026-08-09 07:31:30 +00:00