Skip to content

feat(sdk/daemon-ui): unified completeness follow-up to #4328#4353

Merged
wenshao merged 24 commits into
QwenLM:daemon_mode_b_mainfrom
chiga0:feat/daemon-ui-completeness-followup
May 24, 2026
Merged

feat(sdk/daemon-ui): unified completeness follow-up to #4328#4353
wenshao merged 24 commits into
QwenLM:daemon_mode_b_mainfrom
chiga0:feat/daemon-ui-completeness-followup

Conversation

@chiga0

@chiga0 chiga0 commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Unified follow-up to #4328 — closes every SDK-only gap from the unified-renderer-layer review so library-embedder consumers (web chat, web terminal, and any third-party host built on @qwen-code/sdk/daemon + @qwen-code/webui) all render the same transcript the same way. Native TUI, channel adapters (DingTalk / Telegram / WeChat), and IDE companions stay on their existing direct ACP paths and are NOT in this PR's adoption scope (see docs/developers/daemon-ui/README.md).

#4328 shipped the v1 transcript-layer skeleton (~55%). This PR brings the daemon UI surface to ~95% completeness — the remaining 5% is daemon/Core work outside the SDK package, declared in TODO §B / §D below.

What this PR delivers

1. Full daemon event coverage (was 13 types → now 28+)

The normalizer used to fall back to debug for 16 of the daemon's emitted event types (session-meta, workspace Wave 3/4, auth device-flow, etc.). Adapters had no way to dispatch on them without grepping debug.text. This PR types every one — session.metadata.changed, session.approval_mode.changed, workspace.mcp.budget_warning, auth.device_flow.failed, and so on — with closed-enum fields where the daemon protocol defines them (errorKind, provenance, serverId).

Benefit: adapters get a typed discriminated union to switch on. Forward-compat for new daemon events still routes through debug cleanly; no exhaustiveness failures.

2. Cross-client time consistency

DaemonUiEventBase.serverTimestamp? + DaemonTranscriptBlockBase.clientReceivedAt, plus selectTranscriptBlocksOrderedByEventId (daemon-monotonic ordering) and formatBlockTimestamp (Intl-based locale-aware formatter).

Benefit: when multiple clients attach to the same session, "X minutes ago" labels and block ordering stay consistent regardless of each client's local clock drift. Survives SSE replay-after-reconnect because the daemon's eventId is the primary sort key.

3. Reducer state machine — currentTool / approvalMode / cancellation

DaemonTranscriptState now tracks sidechannel state alongside the block list:

  • currentToolCallId — which tool is in-flight right now (auto-maintained on tool lifecycle transitions)
  • approvalMode — mirrored from session.approval_mode.changed
  • toolProgress — ready for the (still-pending) tool.progress event
  • Cancellation propagation: when assistant.done.reason === 'cancelled', every in-flight tool's status flips to 'cancelled' automatically — daemon doesn't guarantee a terminal tool_call_update for every in-flight tool when the parent prompt is cancelled

Benefit: UIs stop showing "tool spinning forever" after cancel. Renderers can read selectCurrentTool(state) instead of scanning blocks. New selectSubagentChildBlocks exposes sub-agent delegation as a queryable tree (via the daemon's _meta.parentToolCallId stamp).

4. Render contract — markdown / HTML / plain text

daemonBlockToMarkdown / daemonBlockToHtml / daemonBlockToPlainText / daemonToolPreviewToMarkdown — four projection helpers that take a block and return a renderable string. Conservative HTML sanitizer (ANSI strip → HTML escape; role="alert" for errors). sanitizeUrls strips token-shaped query params from CDN/auth URLs. maxFieldLength truncation caps any single field at 8192 chars by default.

Benefit: web chat, web terminal, IDE extension, and any future adapter all render identically by default. Adapters opt into custom rendering per block.kind / preview.kind only where they need it. No more per-adapter projection drift.

5. Tool preview taxonomy — 4 → 13 kinds

file_diff · file_read · web_fetch · mcp_invocation · code_block · search · tabular · image_generation · subagent_delegation · ask_user_question · command · key_value · generic. Each detected from tool input shape; each with a markdown + plain-text projection.

Benefit: tool calls render with appropriate per-kind affordances (unified diff for edits, MCP server badge for MCP calls, image thumbnail for generation tools, etc.) without each adapter writing its own switch.

6. Adapter conformance framework

runAdapterConformanceSuite(adapter) + an embedded fixture corpus (11 fixtures including subagent nesting, redaction, cancellation, mcp-budget, auth-device-flow). Adapters run this in their own test suite and surface projection drift before users see it.

Benefit: when a new daemon event or preview kind lands, every adapter that runs conformance sees a failing fixture instead of silently displaying nothing — projection drift is caught in CI, not in user reports.

7. WebUI migration

packages/webui's transcriptAdapter now bridges through the SDK render contract. Opt-in flags (useMarkdown, enrichToolDetailsWithPreview) preserve legacy default behavior for incremental rollout.

Benefit: web chat starts consuming the shared render layer immediately; rich previews (file diffs, MCP, tabular) surface without webui adding kind-specific components.

8. Sensitive-field redaction at the normalizer boundary

redactSensitiveFields walks tool input/output/content/locations and redacts values for apiKey / token / secret / password / authorization / cookie / bearertoken / accesstoken etc. (closed list, normalized for case/separators) before they reach transcript blocks.

Benefit: a buggy debug panel or naive JSON.stringify(block) can't leak credentials. Tests verify end-to-end (Bearer secret-do-not-leak never appears in any serialized event).

9. Sub-agent nesting

When the daemon stamps _meta.parentToolCallId + _meta.subagentType on a tool call (the Task-equivalent delegation pattern), the reducer correlates child tool blocks under their parent (parentBlockId). Out-of-order arrival (child before parent) is handled — back-fill happens when the parent appears, or when a later child update arrives.

Benefit: renderers can draw nested sub-agent activity (folder-header + indented children) without re-correlating on every render. selectSubagentChildBlocks(state, parentId) returns direct children in O(1) after first build.

10. Performance & correctness hardening

Lazy copy-on-write in the reducer (state.blocks reference preserved across sidechannel-only dispatches → WeakMap caches for sort + children-index actually hit). Cancellation iterates only non-trimmed entries. Tool progress + permission block index pruned post-trim to bound memory in long sessions.

Benefit: useSyncExternalStore consumers don't pay an O(n log n) re-sort on every dispatch when only metadata changed.

11. Adapter author documentation

docs/developers/daemon-ui/README.md — full API reference with cookbook (markdown / HTML / plain-text rendering, sub-agent nested rendering, sensitive-field handling, time formatting). docs/developers/daemon-ui/MIGRATION.md — 9-step before/after guide for adapter authors.

Benefit: lowers cost of bringing a new adapter (channel plugin, IDE extension, dashboard) onto the shared contract from "read 600 LOC of normalizer source" to "run runAdapterConformanceSuite + read the cookbook".

Daemon-side dependency status (verified against daemon_mode_b_main @ 57d04786d)

After landing #4360 (daemon protocol completion), 5 of 7 declared dependencies are now satisfied on the wire — meaning the forward-compat code paths in this PR activate automatically once merged:

Item Daemon-side SDK-side (this PR)
_meta.serverTimestamp envelope stamping server.ts:2670 (cites issue #19 P0) ✅ 3-location probe + formatBlockTimestamp
provenance + serverId on tool_call ToolCallEmitter.emitStart ✅ heuristic + explicit stamp consumer
errorKind on stream_error server.ts:2046 DaemonUiErrorEvent.errorKind typed
errorKind on session_died ⚠ Equivalent: closed-enum reason field ✅ reads reason
Subagent nesting (_meta.parentToolCallId) SubAgentTracker.getSubagentMeta() ✅ reducer + selectSubagentChildBlocks
tool.progress event ❌ Daemon not emitting yet ✅ state shape ready
Multimodal echo (MessageEmitter.emitUserContent) ❌ Core still text-only extractContentPart ready

Validation

# SDK
cd packages/sdk-typescript
npx vitest run test/unit/daemonUi.test.ts    # 162/162 pass
npx tsc --noEmit                              # no errors

# WebUI
cd packages/webui
npx tsc --noEmit                              # no errors

Reference adapter conformance:

runAdapterConformanceSuite({
  reduce: (events) => reduceDaemonTranscriptEvents(createDaemonTranscriptState(), events),
  renderToText: (s) => s.blocks.map(daemonBlockToMarkdown).join('\n\n'),
});
// → { passed: 11, failed: [], total: 11 }

Remaining (deferred to follow-up PRs, not blockers for this one)

  • §B2 tool.progress — new SSE event type (~50 LOC daemon). SDK state shape already ready.
  • §D Multimodal echoMessageEmitter.emitUserContent(parts) + HistoryReplayer inlineData / fileData branches (~80 LOC Core) + reducer wiring (~80 LOC SDK). SDK's extractContentPart helper already shipped, awaiting Core.

Both unblock specific UX features (long-task progress display + image/audio attachment echo); neither blocks this PR's render-contract delivery.

Scope / Risk

  • Scale: ~7400 LOC additive against daemon_mode_b_main (21 files). All changes are additive to the public API; no existing export removed or renamed. createdAt preserved as @deprecated alias for clientReceivedAt.
  • Backward-compat: every existing v1 consumer continues to work unchanged. New behavior is opt-in via additional parameters / new fields.
  • Forward-compat: SDK degrades gracefully when daemon-side fields are absent (heuristic fallbacks, undefined skips, etc.). Unknown event types still route through debug.
  • Browser-safe: the @qwen-code/sdk/daemon subpath has zero React / zero Node-only deps (asserted in assertBrowserSafeBundle). Web-terminal / web-chat bundles include only the helpers they import (tree-shake friendly).

Dependencies

Linked

cc @wenshao @doudouOUC


Generated with assistance from Claude Opus 4.7. Full SDK + WebUI typecheck + 153 unit tests pass against the rebased branch HEAD.

@github-actions

Copy link
Copy Markdown
Contributor

📋 Review Summary

This PR delivers a comprehensive follow-up to #4328, implementing a unified daemon UI layer across 5 coordinated commits (PR-A through PR-E). The changes introduce typed event schemas, server-side timestamps, state machine tracking, tool preview taxonomy, and render contract helpers. Test coverage is strong (77/77 passing), and the implementation demonstrates solid architectural thinking around forward-compatibility and cross-client consistency.

🔍 General Feedback

Positive aspects:

  • Excellent commit organization—each of the 5 commits is independently reviewable and addresses a specific gap
  • Strong forward-compatibility patterns throughout (3-location timestamp extraction, unknown status handling)
  • Comprehensive test coverage with defensive edge cases (ANSI stripping, C1 controls, bidi characters)
  • Clear separation between sidechannel state and transcript blocks
  • Well-documented roadmap and gap-closing rationale in commit messages

Architectural decisions:

  • Monotonic eventId as primary ordering key with serverTimestamp fallback is sound
  • Deliberate deferral of subagent nesting design shows good judgment
  • Tool provenance heuristic (mcp__<server>__<tool>) is pragmatic

Potential concerns:

  • Large diff (+6956/-1991 across 34 files) makes holistic review challenging
  • Some files deleted (DaemonTuiAdapter) while new SDK files added—ensure no functionality regression
  • Heavy reliance on AI co-authorship—verify all type safety and edge cases manually

🎯 Specific Feedback

🟡 High

  • packages/sdk-typescript/src/daemon/ui/store.ts — The reducer handles many event types but lacks explicit handling for all 28+ DaemonUiEventType variants. Verify that session-meta, workspace, and auth events properly update sidechannel state without unintended no-ops.

  • packages/webui/src/daemon/transcriptAdapter.ts:144-156normalizeToolStatus defaults unknown statuses to 'in_progress'. While the comment mentions forward-compat, this could cause future statuses like 'paused' to incorrectly display as active. Consider returning a distinct 'unknown' status or leaving the pointer untouched as PR-E does for currentToolCallId.

  • packages/sdk-typescript/src/daemon/ui/transcript.ts — The propagateCancellationToInFlightTools function walks all blocks to mark in-flight tools as cancelled. For long sessions with many tools, this could be O(n) on every cancel. Consider maintaining an index of in-flight tool IDs for O(1) lookup.

🟢 Medium

  • packages/sdk-typescript/src/daemon/ui/types.ts:58-72 — The DaemonTranscriptBlockBase has both serverTimestamp? and clientReceivedAt with a deprecated createdAt alias. While well-documented, this creates three timestamp fields that could confuse consumers. Consider consolidating documentation or providing a single getTimestamp() helper that returns the most authoritative available value.

  • packages/sdk-typescript/src/daemon/ui/utils.ts — The extractContentPart function handles multimodal content but silently returns undefined for unknown types. This is defensive but could hide daemon evolution. Consider logging unknown content kinds to debug output for observability.

  • packages/webui/src/daemon/DaemonSessionProvider.tsx:155-175 — The getReconnectDelayMs function implements exponential backoff but doesn't expose jitter. In a thundering herd scenario (many clients reconnecting simultaneously), synchronized retries could overload the daemon. Add optional randomization (e.g., ±20% jitter).

  • packages/sdk-typescript/src/daemon/ui/terminal.ts — The daemonUiEventToTerminalText function handles many event types but the switch statement is lengthy. Consider extracting per-kind handlers into separate functions for better testability and readability.

🔵 Low

  • packages/sdk-typescript/src/daemon/ui/types.ts:104 — The DaemonUiToolProvenance type includes 'unknown' as a catch-all. Consider adding JSDoc examples of when each provenance is assigned, especially the heuristic fallback logic for mcp__ prefix detection.

  • packages/webui/src/types/toolCall.ts:10-16 — The ToolCallStatus union now includes 'cancelled', but existing tool call components (GenericToolCall, ShellToolCall, etc.) may need updates to handle the new status visually. Verify all consumers render cancelled state appropriately.

  • packages/sdk-typescript/src/daemon/ui/render.ts — The daemonBlockToMarkdown and daemonBlockToHtml functions accept opts? but default parameter handling could be clearer. Consider using explicit default options object pattern for better discoverability.

  • packages/webui/vite.config.ts:23-30 — The alias configuration duplicates the tsconfig.json paths. While necessary for Vite, consider documenting this duplication or extracting to a shared config to avoid drift.

  • docs/developers/daemon-client-adapters/tui.md — This file is deleted. Ensure the new web-ui.md documentation covers equivalent guidance for TUI consumers, or migrate relevant content rather than removing.

✅ Highlights

  • Event coverage expansion (13→28 types) — Comprehensive typing for session-meta, workspace, and auth events closes significant gaps in daemon observability

  • Server timestamp extraction — The 3-location forward-compat pattern (event.serverTimestamp, event._meta.serverTimestamp, event.data._meta.serverTimestamp) is elegantly designed for gradual daemon adoption

  • Security sanitizationsanitizeDaemonTerminalText handles ANSI escapes, C1 controls, OSC/DCS sequences, and bidi characters comprehensively. HTML output escapes XSS vectors while preserving content integrity.

  • Tool preview taxonomy — The 8-kind DaemonToolPreview union (file_diff, file_read, web_fetch, mcp_invocation, etc.) provides rich structured display without over-engineering

  • Cancellation propagation — The propagateCancellationToInFlightTools logic prevents infinite spinner scenarios when daemon doesn't guarantee terminal events for all tools on cancel

  • Test quality — Tests cover edge cases like secret field redaction, malformed payloads, protocol version mismatches, and ANSI control sequence stripping

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A deterministic typecheck also reports TS4111 in packages/webui/src/components/toolcalls/ShellToolCall.tsx for existing Record<string, unknown> dot-property accesses (description / command). Those lines are not part of the PR diff, so I am not posting them as inline comments, but the changed-file typecheck will still need to be clean before merge.

— gpt-5.5 via Qwen Code /review

Comment thread packages/webui/src/daemon/transcriptAdapter.test.ts
Comment thread packages/webui/src/index.ts
Comment thread packages/webui/src/daemon/DaemonSessionProvider.tsx
Comment thread packages/webui/src/daemon/DaemonSessionProvider.tsx
@chiga0
chiga0 requested review from doudouOUC and yiliang114 May 20, 2026 07:56
Comment thread packages/sdk-typescript/src/daemon/index.ts
Comment thread packages/sdk-typescript/src/daemon/ui/transcript.ts Outdated
Comment thread packages/sdk-typescript/src/daemon/ui/normalizer.ts Outdated
Comment thread packages/sdk-typescript/src/daemon/ui/store.ts Outdated
Comment thread packages/webui/src/daemon/transcriptAdapter.ts
Comment thread packages/webui/src/daemon/DaemonSessionProvider.tsx
Comment thread packages/sdk-typescript/src/daemon/ui/render.ts
Comment thread packages/sdk-typescript/src/daemon/ui/types.ts Outdated
Comment thread packages/sdk-typescript/src/daemon/ui/transcript.ts
Comment thread packages/sdk-typescript/src/daemon/ui/transcript.ts Outdated
chiga0 pushed a commit to chiga0/qwen-code that referenced this pull request May 20, 2026
…ete (PR-F)

Closes the "5 additional preview kinds" item in PR QwenLM#4353's TODO §A
(SDK-only work).

## New preview kinds (8 → 13)

- `code_block` — `{ language?, code, origin? }` — REPL / formatter /
  generator output, fenced as `\`\`\`<language>` in markdown
- `search` — `{ query, resultCount?, top? }` — grep / ripgrep / find /
  glob results with up to 5 top hits
- `tabular` — `{ columns, rows, totalRows? }` — structured table output
  (50-row cap with `totalRows` truncation indicator); supports both
  `columns: string[] + rows: unknown[][]` explicit shape and legacy
  `data: Array<Record<>>` shape (auto-infers columns from first row)
- `image_generation` — `{ prompt, thumbnailUrl?, model? }` — dall-e /
  diffusion / imagen / flux / sora style tools
- `subagent_delegation` — `{ agentName, task, parentDelegationId? }` —
  Anthropic-style Task tool and similar sub-agent dispatchers

## Detector priority

Order matters — most specific wins. New detectors slot in between
`mcp_invocation` and `file_diff`:

```
mcp_invocation > subagent_delegation > search > image_generation
  > file_diff > file_read > web_fetch > code_block > tabular
  > command > key_value > generic
```

Rationale: subagent / search / image generation are most discriminable
(distinct toolName patterns); file ops next; code_block / tabular last
because their shapes (`code:`, `columns:`) can appear in other tools.

## Render projections

Both `daemonToolPreviewToMarkdown` and the plain-text rendering paths
extended with cases for all 5 new kinds:

- code_block: fenced markdown code block with language tag
- search: bold header + GFM bullet list of top results
- tabular: GFM pipe table with header / separator / body / truncation hint
- image_generation: bold header + blockquoted prompt + embedded markdown
  image (URL sanitization respected via `sanitizeUrls` opt)
- subagent_delegation: bold delegate-arrow header + blockquoted task +
  optional parent delegation reference

## Test coverage (91/91 pass, +14 new)

- Each detector with positive case
- Detector priority verified: subagent_delegation wins over file_diff
  when toolName='Task' has both subagent + file-edit fields
- Tabular row cap (50) + totalRows stamping for truncated data
- Legacy data: Array<Record<>> auto-column inference
- Each render projection with structural assertions (markdown table
  format, image embed, bullet lists)

## Roadmap

PR-F of the unified follow-up to PR QwenLM#4328. Brings the preview taxonomy
to 13 kinds covering: file ops (3), web (1), code/data (2), media (1),
agent control (2 — ask_user_question + subagent_delegation), MCP (1),
search (1), generic fallbacks (2).

Generated with AI

Co-authored-by: Claude Opus 4.7 <[email protected]>
chiga0 pushed a commit to chiga0/qwen-code that referenced this pull request May 20, 2026
…PR-G)

Closes the "Adapter conformance test framework" item in PR QwenLM#4353's TODO §A.
Lets any daemon-ui adapter (TUI / web / IDE / channel / mobile) validate
that it projects a fixed corpus of daemon SSE event streams to the same
semantic shape — catches projection drift before it reaches users.

## API surface

```ts
interface DaemonUiAdapterUnderTest {
  reduce(events: readonly DaemonUiEvent[]): unknown;
  renderToText(state: unknown): string;
}

interface DaemonUiConformanceFixture {
  name: string;
  description: string;
  envelopes: DaemonEvent[];           // raw daemon envelopes
  expectedContains: string[];          // phrases the rendered text MUST contain
  expectedAbsent?: string[];           // phrases that MUST NOT appear
  normalizeOptions?: { ... };          // forward-compat normalize opts
}

runAdapterConformanceSuite(adapter, opts?): ConformanceSuiteResult
DAEMON_UI_CONFORMANCE_FIXTURES: ReadonlyArray<DaemonUiConformanceFixture>
```

## Design

**Format-agnostic assertion**: adapters can render to ANSI / HTML /
markdown / JSX — the framework only inspects plain text via
`renderToText`. Catches semantic divergence (missing user message,
wrong tool status, leaked secret) without forcing identical formatting.

**Embedded fixture corpus** (no fs reads — works in browser bundle):
- `simple-chat` — user/assistant streaming flow
- `tool-call-lifecycle` — running → completed transition
- `file-edit-diff` — file_diff preview surfacing
- `mcp-invocation` — MCP serverId/toolName extraction via heuristic
- `permission-lifecycle` — request + resolved with outcome
- `mcp-budget-warning` — Wave 3 event (adapter must observe but rendering
  is its choice)
- `cancellation-propagates` — tool block status flows
- `malformed-payload-redaction` — uses `includeRawEvent: true` to verify
  even a debug-mode adapter doesn't leak `token: secret-do-not-leak`
- `auth-device-flow-success` — Wave 4 OAuth events
- `available-commands-typed-event` — PR-A upgrade from status text

Per-fixture `expectedContains` and `expectedAbsent` describe the
content contract independently of format.

## Suite result

```ts
{
  passed: number,
  failed: ConformanceFailure[],   // each carries missing + leaked + excerpt
  total: number,
}
```

**Does not throw** — caller asserts on `result.failed` so adapter test
suites can produce per-fixture diagnostics rather than a single opaque
exception.

## Filter options

`only` / `skip` allow targeted runs during adapter development:

```ts
runAdapterConformanceSuite(myAdapter, { only: ['simple-chat'] });
runAdapterConformanceSuite(myAdapter, { skip: ['cancellation-propagates'] });
```

## Test coverage (97/97 pass, +6 new)

- SDK reference adapter (reducer + markdown render) passes all fixtures
- SDK reference adapter (reducer + plainText render) also passes
- Buggy adapter (empty string output) fails every fixture with non-empty
  `expectedContains`
- Buggy adapter (raw event dump via JSON.stringify) caught by redaction
  fixture's `expectedAbsent`
- `only` filter narrows to a single fixture
- `skip` filter excludes named fixtures from the corpus

## Usage from adapter authors

```ts
// In your adapter's test file
import { runAdapterConformanceSuite } from '@qwen-code/sdk/daemon';
import { reduceForTui, renderTuiState } from './my-tui-adapter';

it('TUI adapter conforms to daemon UI corpus', () => {
  const result = runAdapterConformanceSuite({
    reduce: reduceForTui,
    renderToText: renderTuiState,
  });
  expect(result.failed).toEqual([]);
});
```

## Roadmap

PR-G of the unified follow-up to PR QwenLM#4328. The corpus is intentionally
small (10 fixtures) but extensible — adapter authors can submit new
fixtures via additions to `DAEMON_UI_CONFORMANCE_FIXTURES` to lock in
regression coverage for edge cases their adapter encountered.

Generated with AI

Co-authored-by: Claude Opus 4.7 <[email protected]>
chiga0 pushed a commit to chiga0/qwen-code that referenced this pull request May 20, 2026
…act (PR-H)

Closes the "WebUI transcriptAdapter migration" item in PR QwenLM#4353's TODO §A.
Validates the PR-D render contract end-to-end on the real WebUI consumer.

## Migration approach — additive opt-in

`daemonTranscriptToUnifiedMessages(blocks, options?)` gains a new options
parameter:

```ts
interface DaemonTranscriptAdapterOptions {
  useMarkdown?: boolean;                  // default: false
  enrichToolDetailsWithPreview?: boolean; // default: false
}
```

Defaults preserve legacy behavior — existing callers see no change.

## What `useMarkdown: true` does

For `user` / `assistant` / `thought` blocks, content is projected via
SDK's `daemonBlockToMarkdown` instead of raw sanitized text. The WebUI's
markdown renderer (markdown-it) then gets:

- `**You**\n\n<content>` for user blocks (bold "You" label)
- Raw text for assistant blocks (markdown formatting in agent output
  passes through cleanly)
- `> *thought:* <text>` blockquote for thought blocks

## What `enrichToolDetailsWithPreview: true` does

For `tool` blocks, `rawOutput` is replaced with `daemonToolPreviewToMarkdown(block.preview)`.
This lets WebUI surfaces without per-preview-kind React components still
display:

- `file_diff` as a fenced unified diff
- `mcp_invocation` as `server::tool` with args summary
- `tabular` as GFM pipe table
- `search` as bullet list with match count
- `image_generation` as embedded markdown image
- `subagent_delegation` as delegate arrow + task quote

Renderers with per-kind components should leave this opt-out.

## SDK daemon root index.ts re-exports

`packages/sdk-typescript/src/daemon/index.ts` was missing exports for
PR-D / PR-F / PR-G / PR-B / PR-E surface — WebUI's `@qwen-code/sdk/daemon`
import path uses the daemon root, not the ui/ sub-index. Added 15+
re-exports so consumers don't need to use the longer
`@qwen-code/sdk/daemon/ui/index.js` path.

Now exported from `@qwen-code/sdk/daemon` root:
- `daemonBlockToMarkdown` / `daemonBlockToHtml` / `daemonBlockToPlainText`
- `daemonToolPreviewToMarkdown`
- `extractContentPart` + `DaemonUiContentPart` type
- `formatBlockTimestamp` + `selectTranscriptBlocksOrderedByEventId`
- `selectCurrentTool` / `selectApprovalMode` / `selectToolProgress`
- `runAdapterConformanceSuite` + `DAEMON_UI_CONFORMANCE_FIXTURES`
- All associated types

## Test fixture migration

`webui/src/daemon/transcriptAdapter.test.ts` mock blocks updated to include
`clientReceivedAt` (required field added in PR-B). Mechanical change —
every `createdAt: N` test fixture gets a matching `clientReceivedAt: N`.

## Validation

- WebUI `npm run typecheck` — clean
- SDK `npm run typecheck` — clean
- SDK `vitest run test/unit/daemonUi.test.ts` — 97/97 pass
- WebUI transcriptAdapter test fixtures typecheck against updated
  DaemonTranscriptBlockBase schema

## Roadmap

PR-H of the unified follow-up to PR QwenLM#4328. Closes the WebUI migration
gap in TODO §A.

Generated with AI

Co-authored-by: Claude Opus 4.7 <[email protected]>
chiga0 pushed a commit to chiga0/qwen-code that referenced this pull request May 20, 2026
Closes the final "Documentation" item in PR QwenLM#4353's TODO §A. Brings the
unified daemon UI surface to ~95% SDK-side completion.

## Files added

- `docs/developers/daemon-ui/README.md` — full API reference
  - Three-layer model (normalizer → reducer → render helpers)
  - Quick start with idiomatic event-loop pattern
  - Event taxonomy (28+ types categorized: chat-stream / session-meta /
    workspace / auth device-flow)
  - Render contract cookbook (markdown / HTML / plainText)
  - Tool preview taxonomy (13 kinds with use cases)
  - State selectors (currentTool / approvalMode / toolProgress / ordering)
  - Cancellation propagation explanation
  - Time semantics (eventId > serverTimestamp > clientReceivedAt
    precedence)
  - Adapter conformance usage
  - ErrorKind dispatch pattern
  - Tool provenance dispatch pattern
  - Forward-compat principles

- `docs/developers/daemon-ui/MIGRATION.md` — adapter author migration
  cookbook
  - Step-by-step recommended adoption order (9 steps, value-ranked)
  - Before/after code examples for each step
  - Backward-compat checklist (everything is additive — no breaking
    changes)
  - Cross-references to PR-A through PR-H commits

## Roadmap

PR-I of the unified follow-up to PR QwenLM#4328. Documentation-only — no
code changes; no tests affected.

Generated with AI

Co-authored-by: Claude Opus 4.7 <[email protected]>
@chiga0

chiga0 commented May 20, 2026

Copy link