mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-24 08:24:07 +00:00
3 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
170ce7917d
|
feat(web-shell): show Settings and Daemon Status as an in-place panel (#6341)
* feat(web-shell): show Settings and Daemon Status as an in-place panel
The Settings and Daemon Status buttons opened centered modal overlays that
dimmed the whole app. Render them as a full-height panel that replaces the
chat surface instead — a Back button or Escape returns to the chat — with the
content left-aligned and filling the chat pane width rather than centered in a
narrow column. On the Daemon Status page, give the time-series charts a taller
plot and the overview cards a wider track so a wide window is actually used;
the tab grouping (overview / metrics / diagnostics) is kept.
* fix(web-shell): preserve composer draft and refine panel focus/escape
Address review feedback on the in-place Settings / Daemon Status panel:
- Keep the chat view (message list + composer) mounted and just hidden while a
panel is shown, so typing a prompt, opening Settings/Status, then going Back
no longer discards the unsent draft and attachments (the composer subtree was
being unmounted and remounted empty).
- Focus the Back button when a panel opens and restore focus to the composer
when it closes, replacing the focus management DialogShell used to provide.
- Reload workspace settings after the fast-model command resolves so the still-
mounted Settings panel doesn't keep showing the previous value.
- Don't close the panel when Escape is handled inside the sidebar (its search
input clears on Escape without stopping the event).
- Guard the overview card grid with min(100%, 340px) so a 340px track can't
overflow a narrow panel.
* fix(web-shell): reset panel scroll when switching Settings/Status
Add key={activePanel} on the panel body so switching directly between Settings
and Daemon Status (activePanel goes 'settings' -> 'status' without passing
through null) remounts the scroll container and opens at the top instead of
inheriting the previous panel's scrollTop.
* fix(web-shell): restore new-chat vertical centering
The chatViewWrap added to keep the composer mounted while a panel is shown was
filling the pane, which cancelled the empty (new-chat) state's vertical
centering (welcome header + composer stuck to the top instead of centered).
Shrink the wrapper to its content in the empty state so
`.appChatEmpty .chatPane { justify-content: center }` centers it again, matching
how `.content` behaves there.
* fix(web-shell): restore session-org test destructuring dropped in merge
My earlier merge of origin/main 3-way-combined WebShellSidebar.test.tsx
incorrectly, dropping the `{ }` destructuring from the 7 session-organization
tests. renderSidebar returns `{ container, rerender }` (not the element), so
`const container = renderSidebar(...)` made `container.querySelector` throw.
Restore the file to origin/main so the tests pass again.
* fix(web-shell): surface pending approvals over Settings/Status panel
The in-place Settings/Daemon Status panel hides the chat footer with
display:none, and the ToolApproval / AskUserQuestion overlays live in
that footer. A gated tool call that arrived while a panel was open
rendered the approval into a hidden container, so the turn hung with no
visible prompt (reported [Critical]).
Close the panel when an actionable approval is pending so it surfaces.
Only actionable approvals count (pendingToolApproval / pendingAskUserApproval
already gate on canActOnPendingApproval), so a non-owner in a shared
session isn't yanked out of Settings by someone else's prompt. Leave
focus for the overlay rather than the composer on this path: ToolApproval
uses a window-level key handler that ignores editable targets, so
focusing the composer would swallow its shortcuts.
Also refocus the Back button on panel->panel switches (not just on open),
so focus no longer relies on the Back button being the same DOM node
across the keyed panel body, and expose SvgLineChart's plot height as
--chart-height (fallback 140px) so a constrained caller can shrink it.
Adds App-level tests: the panel auto-closes on a pending tool approval,
and stays open when the only block is a resolved (non-actionable) one.
* test(web-shell): cover AskUserQuestion auto-close; robust panel selector
Follow-up to the approval auto-close fix, addressing review suggestions:
- Add data-testid="inline-panel" to the panel <section> and query by it in
App.test instead of querySelector('section'), which would false-positive if
any future component renders a <section>.
- Add a companion test for the pendingAskUserApproval branch of the auto-close
effect. The ask-user block carries toolCall.input.questions so
isAskUserPermission() classifies it as an AskUserQuestion; mutation-checked
(restricting the effect to pendingToolApproval fails only this test).
- Document why handleFastModelSelect's post-sendPrompt reload is not racy:
sendPrompt awaits waitForAcceptedPromptCompletion, so the /model --fast turn
has already applied when reloadWorkspaceSettings() runs. Also note why the
command path needs the explicit reload that the setWorkspaceSetting pickers
(vision/voice) get for free via the settingsVersion signal.
* fix(web-shell): close inline panel when resuming a session
The /resume <id> command and the ResumeDialog onSelect both call
loadSession() without closePanel(), unlike createNewSession and
loadSidebarSession. Loading a session means the user wants that chat, so
leaving a Settings/Daemon Status panel open would hide it. Add closePanel()
to both paths for consistency (a no-op when no panel is open).
Currently reachable only in theory — the composer that submits /resume is
display:none while a panel is shown — but the guard keeps the invariant if
a non-composer entry point (sidebar/shortcut) is ever added.
Adds a mutation-checked test (removing closePanel from the /resume handler
fails it) and gives the ChatEditor test mock a focus() method, since the
panel-close focus effect now runs editorRef.focus() on this no-approval path.
* fix(web-shell): keep composer dormant while an approval overlay is up
Follow-up to the approval auto-close fix. When an approval arrives while a
Settings/Status panel is open, the auto-close clears activePanel, so
interactionBlocked flips false and useComposerCore's dialogOpen effect
refocuses the still-mounted composer. ToolApproval ignores approval
shortcuts from editable targets, so the now-visible approval stops
responding to Enter/Escape/number keys until focus moves away.
Key the ChatEditor dialogOpen prop off the pending approval too, so the
composer stays blurred while an approval owns the keyboard. Consolidate the
"an approval overlay is active" condition — previously duplicated in the
auto-close effect and the panel focus guard — into a single
approvalOverlayActive value so the three consumers can't drift.
Uses the actionable-approval condition (pendingToolApproval /
pendingAskUserApproval) rather than raw pendingApproval, matching the
auto-close gating and avoiding a blurred composer when a non-actionable
approval renders no overlay.
Adds a mutation-checked test asserting dialogOpen is true while an approval
is pending with no panel open.
* test(web-shell): cover the Daemon Status panel branch
A review note observed that all five panel tests open via /settings, so the
activePanel === 'status' branch (DaemonStatusDialog) had no coverage — a
regression in the 'status' literal would go undetected. Add a test that
opens Daemon Status through the sidebar and asserts the panel opens and
auto-closes on a pending approval, exercising that branch and confirming the
auto-close is panel-type-agnostic.
Gives the WebShellSidebar test mock an onOpenDaemonStatus button, since
there is no slash command to open the status panel. Mutation-checked:
neutering setActivePanel('status') fails only this test.
* fix(web-shell): dismiss stacked dialogs on approval, focus overlay, a11y
Addresses a review round on the inline Settings/Status panel.
[Critical] When an approval arrives while a DialogShell sub-dialog (model
picker / approval-mode picker) is open over the panel, the auto-close
removed the panel but left the sub-dialog backdrop covering the footer
approval overlay, so the turn hung. Worse, the approval-mode picker stayed
usable — selecting "yolo" auto-approved (handleSetMode) a tool call the
user never saw. Dismiss the panel AND both sub-dialogs when an actionable
approval is pending.
[Critical] After the panel closes for an approval, focus fell to <body>
(the Back button unmounted; ToolApproval has no autofocus). Move focus onto
the ToolApproval overlay wrapper (tabindex=-1, so its window-listener
shortcuts keep working and Enter doesn't confirm early) once it is visible.
AskUserQuestion keeps managing its own focus.
Suggestions:
- Guard reloadWorkspaceSettings() rejection (was unhandled).
- aria-hidden the display:none chat view so AT can't wander into it.
- Close the panel before /model --fast so its response shows in the chat
in context instead of piling up behind the hidden panel.
- Remove the dead .panelBodyInner wrapper (redundant width:100%).
- Rename data-mobile-drawer -> data-sidebar-shell (it wraps the desktop
sidebar for all viewports) across App.tsx + standalone.css.
Tests (+5, mutation-checked): sub-dialog dismissal on approval, overlay
focus, Escape closes panel / sidebar-Escape does not, dialogOpen while a
panel replaces the chat. Adds an observable DialogShell test mock.
* fix(web-shell): restore composer focus after approval resolves; cleanups
[Critical] The panel focus effect consumed prevActivePanelRef to null on the
approval auto-close (correctly skipping editor focus then). When the approval
later resolved with no panel to return to, neither branch fired and focus was
left on <body> — the visible composer took no keyboard input. Track the
approvalOverlayActive transition too and restore composer focus on
approval-resolve-after-panel-close. (useComposerCore's dialogOpen effect also
covers this; the extra branch makes the panel effect self-contained.)
Suggestions:
- Log the reloadWorkspaceSettings() rejection instead of swallowing it.
- Remove the dead :global([data-dialog-fullscreen]) .grid rule (the
allowFullscreen source was removed when Daemon Status became a panel).
Tests (+4, focus-restore mutation-checked): focus restored after an approval
resolves post-auto-close; Back-button closes the panel and restores focus;
fast-model pick closes the panel, sends /model --fast, and reloads settings;
chat view is aria-hidden while a panel is shown. Adds interactive
SettingsMessage / ModelDialog mocks and stable editor-focus / settings-reload
spies.
* fix(web-shell): make Settings/Status panel and Scheduled Tasks mutually exclusive
Opening Scheduled Tasks (mainView — a position:absolute z-index overlay) and
then Daemon Status (activePanel) rendered the Daemon Status panel *behind* the
Scheduled Tasks overlay, so the button looked like it did nothing. These are
mutually-exclusive full-pane views, so opening one must close the other.
Centralize into openPanel() / openScheduledTasks() helpers (last-opened wins)
and route all six open sites (the /settings and /schedule commands, the three
sidebar handlers, and the StatusBar) through them.
Mutation-checked tests: opening Daemon Status closes the Scheduled Tasks page
(the reported repro), and opening Scheduled Tasks closes an open panel.
|
||
|
|
52a190b5c6
|
feat(web-shell): time-series metrics charts on Daemon Status (#6307)
* feat(web-shell): time-series metrics charts on Daemon Status
Add seven bottleneck-analysis line charts (concurrency, requests, API
latency, prompt latency, event-loop lag, memory, token burn) to the
Daemon Status dashboard, backed by a new server-side metrics ring.
The status endpoint is a point-in-time snapshot, so line charts need a
time series. A bounded ring buffer in the daemon (daemon-metrics-ring.ts)
seals one bucket every 5s (~15min retained) from three seams:
- HTTP request rate/latency via the telemetry middleware
- prompt queue-wait/duration via the bridge telemetry hooks
- per-round token usage sniffed at the bridge session/update fan-in
(new DaemonBridgeTelemetryMetrics.tokenUsage hook)
plus memory / active sessions+prompts / a window-scoped event-loop lag
p99 read as gauges at seal time.
The series rides the existing GET /daemon/status contract
(runtime.metrics.series), threaded through the SDK types (JSON passthrough)
to a dependency-free inline-SVG chart component in web-shell -- no charting
library added to the CSP-strict serve --web bundle.
Tests: metrics-ring math, token-usage sniffing on the real sessionUpdate
path, and SVG chart rendering. Verified end-to-end against a live daemon
(GLM-5.2): requests/latency/memory/event-loop, real token burn and prompt
duration, with the concurrency gauge tracking active prompts.
* feat(web-shell): tabs, chart tooltips, and fullscreen for Daemon Status
Split the now chart-heavy Daemon Status dashboard into Overview / Metrics /
Diagnostics tabs (status badge, refresh, and issues stay global) so
monitoring, configuration, and troubleshooting each get their own space
instead of one long 70vh scroll.
Add an interactive hover cursor to the charts: a vertical time line, a dot on
each series, and a tooltip reading the bucket time plus every series' value at
that point -- previously only the latest value and peak were legible, from the
legend.
Add an opt-in fullscreen toggle to DialogShell (via allowFullscreen, wired for
Daemon Status) that expands the panel to near the full viewport; scrolling is
consolidated into the shell body so the content actually grows with it.
Tests: tab switching + diagnostics-behind-tab, SVG tooltip rendering, and the
DialogShell fullscreen toggle. Verified end-to-end against a live daemon
(GLM-5.2) with real request / token / prompt data.
* feat(web-shell): add CPU, LLM-latency, queue-depth, IPC & connection metrics
Extend the Daemon Status metrics ring with more bottleneck-analysis
dimensions, filling the two biggest gaps — resource cost had only memory
(no CPU), and latency had only client->daemon HTTP (not daemon->model):
- CPU %: process.cpuUsage() delta, core-normalized (memoryPressureMonitor
formula, clamped 0-100), sampled alongside memory.
- LLM API latency p50/p95: the token frame's _meta.durationMs (the
daemon->model round-trip), separating 'model is slow' from 'we are slow'.
- Prompt queue depth: a new bridge.pendingPromptTotal aggregate, folded into
the concurrency chart beside active tasks.
- IPC pipe throughput: daemon<->ACP-child stdio bytes (already measured; now
windowed via metricsRing.recordPipe).
- Connection counts (SSE/WS/ACP) and rate-limit rejections, read lazily in the
sampler from the ACP handle registry and the rate limiter.
The tokenUsage telemetry hook is widened to carry durationMs. Verified
end-to-end against a live daemon (GLM-5.2): LLM p95 28.6s vs HTTP p95 324ms,
queue depth 1, IPC peak 0.3MB, SSE gauge 1 on a live stream.
* feat(web-shell): add ACP child process CPU/memory (self-reported over ACP)
The daemon's own CPU/memory only tell half the story — the real LLM/tool work
runs in the spawned 'qwen --acp' child, which is where the resource cost lives.
Surface it: the child self-reports its rss + cpuPercent to the daemon over a new
read-only ACP extMethod (qwen/status/workspace/resource); the bridge caches the
latest sample on the live channel, and the metrics sampler reads it
synchronously each tick (firing an async refresh for the next, off the hot path).
The child computes cpuPercent as a process.cpuUsage() delta between polls (no
dependency on MemoryPressureMonitor's tool-gated sampling), core-normalized and
clamped. Rendered as a second line on the CPU and Memory charts (daemon vs
child, side by side).
Verified end-to-end (GLM-5.2): child RSS ~300MB vs daemon RSS ~225MB, child CPU
tracking above the daemon's -- the child is the resource hog, now visible.
* test(web-shell): cover Metrics tab, chart rendering, and the recordRequest seam
Address review — the metrics dashboard's rendering and its HTTP data seam had
no tests:
- DaemonStatusDialog: switching to the Metrics tab renders the charts from the
series (one SvgLineChart per card) and hides the Overview panel; an empty
series shows the collecting-metrics placeholder.
- daemonTelemetryMiddleware: recordRequest fires once with (durationMs,
statusCode) on a matched route (real status code; once across finish/close),
is not called for unmatched routes, and is a silent no-op when omitted.
* fix(web-shell): enlarge Daemon Status charts in fullscreen
Fullscreen widened the panel but the charts stayed small — the grid just packed
in more 280px cards at a fixed 52px SVG height, so the extra viewport bought
more small charts, not bigger ones. Now the DialogShell body carries a
`data-dialog-fullscreen` marker; the chart grid switches to wider cards (min
480px → fewer columns) and the SVG grows to 120px, so fullscreen actually
enlarges the plots. Verified: 2 wide columns at 120px vs 3-4 columns at 52px.
* fix(web-shell): resolve chart colors in portal, guard child-resource polling
Address review (real-user + ci-bot):
- [Critical] Chart colors (--primary, --agent-blue-400) resolved to nothing in
the DialogShell portal (createPortal to document.body escapes the app root that
defines them), so ~half the chart lines rendered stroke:none. Add both vars to
DialogShell's own theme scope. Verified: 25/25 path strokes colored (was 5 none).
- [Critical] refreshChildResource had no in-flight guard; requestWorkspaceStatus
waits up to 10s (> the 5s cadence), so a degraded child accumulated concurrent
polls. Add a single-flight guard.
- [Critical] getChildResourceSnapshot returned last-good rss/cpu forever; add a
30s staleness window so a stuck child reads 0 instead of looking healthy.
- Exclude GET /daemon/status (the dashboard's own poll) from the metrics-ring
request rate, so the Requests chart doesn't count itself.
- Fix cpuPercent JSDoc (percent of total capacity across cores, clamped [0,100])
in the ring + SDK mirror; add a keep-in-sync cross-reference on the mirror.
Tests: recordRequest excludes /daemon/status; buildDaemonStatusResponse embeds
runtime.metrics.series when provided and omits it otherwise.
* fix(web-shell): address Daemon Status charts review feedback
Correctness fixes surfaced in review:
- bridgeClient: guard token accounting on a live `entry`. On the
`session/load` path HistoryReplayer re-emits saved usage as live
session/update frames before the session entry is registered, which
otherwise dumped a session's historical token total into the current
metrics window as a phantom burn spike with no model call.
- run-qwen-serve metrics sampler: wrap each tick in try/catch/finally so a
throwing getter can't crash the daemon; reset the event-loop-lag histogram
in finally so a thrown tick can't permanently discard it; skip the CPU
delta (and leave the baseline untouched) when process.cpuUsage() throws;
seed the rate-reject baseline on the first tick instead of reporting the
whole since-start backlog as one spike.
- acpAgent workspaceResource: advance the child-CPU baseline only on a
successful read, avoiding a ~2x phantom spike on the poll after a failure.
- bridge.pendingPromptTotal: count only queued prompts (state === 'queued'),
not the running one, so the "Queued" chart reflects real backpressure and
no longer shadows the "Active tasks" line.
- Make the new Daemon Status bridge hooks optional in AcpSessionBridge and
optional-chain them in the sampler, so a bridge injected via
RunQwenServeDeps.bridge that predates them degrades gracefully.
Robustness / UX:
- daemon-metrics-ring sanitizes non-finite gauges to 0 so a bad reading
never serializes as JSON null and gaps the chart.
- child-resource refresh logs failures at debug for observability.
- formatBytes drops to KB/B for sub-MB pipe traffic (was "0.0 MB").
- SvgLineChart peak label is now i18n'd (daemon.charts.peak).
- Daemon Status tabs get the full WAI-ARIA tabs pattern: aria-controls,
role=tabpanel, and Arrow/Home/End keyboard navigation with roving tabindex.
Tests: replay token guard (no live entry), pipe/gauge/sample-cap defenses,
large-value legend formatting, and tab keyboard navigation.
* fix(web-shell): keep Daemon Status fullscreen + tooltip correct in dialog portal
Two DialogShell-portal theme-scope issues surfaced by a follow-up review:
- Fullscreen was clamped back to 80vh on narrow screens: the
`@media (max-width: 560px)` `.panel` rule has equal specificity and later
source order than the base `.panelFullscreen`, so it won. Add a media-scoped
`.panelFullscreen` override so fullscreen actually expands on mobile.
- SvgLineChart tooltip background used `var(--popover, var(--card))`, neither of
which the portal theme scope defines, so the declaration dropped and the
tooltip rendered transparent over the chart. Fall back to `--background`
(which the dialog scope does define).
* fix(web-shell): flip chart tooltip below cursor near scroll-container top
The Daemon Status charts live inside DialogShell's overflow-y:auto body, so the
topmost chart's upward tooltip (bottom: calc(100% + 4px)) clipped against the
scroll container's top edge, truncating the time header / first series row on
hover. SvgLineChart now resolves its nearest scroll parent and flips the tooltip
below the cursor when the plot sits within ~one tooltip-height of that clip
boundary.
* fix(daemon-status): harden child-resource CPU/memory + sampler lag on failure
Follow-up review fixes:
- acpAgent: prevChildCpu inits to null (not {0,0}) and the workspaceResource
handler gates the delta on a live prevCpu baseline, so an init-time
cpuUsage() failure no longer manufactures a phantom spike on the first poll
— mirrors the daemon sampler's safeCpuUsage null-on-failure contract.
- acpAgent: guard process.memoryUsage() too, reporting 0 rss on failure while
keeping the already-computed cpuPercent instead of throwing the handler.
- bridge: require Number.isFinite() (typeof NaN === 'number' is true) and
clamp cpuPercent to [0,100] when caching the child's self-report.
- run-qwen-serve sampler: gate the 5s child-resource refresh on an active
SSE/WS client (idle staleness already reads 0), and hoist the event-loop
lag read before the try so a thrown tick charts the real accumulated lag
instead of a misleading 0.
* fix(daemon-status): protect artifact path from metrics callback + share CPU delta
Follow-up review fixes:
- bridgeClient: wrap recordLiveTokenUsage in try/catch so a throwing injected
onTokenUsage callback can't skip the critical artifact processing after it —
metrics are optional, artifacts are not.
- Extract computeCpuPercent() into daemon-metrics-ring and share it between the
daemon self-sampler and the ACP child's workspaceResource handler, removing
the duplicated delta/normalize/clamp math and giving it direct unit coverage
(null sample, non-positive window, normalization, phantom-spike + negative
clamps).
- Add a single-flight test for bridge.refreshChildResource (two rapid calls
collapse to one in-flight RPC).
|
||
|
|
9b2fb30cb0
|
feat(web-shell): add a daemon status page backed by GET /daemon/status (#6272)
* feat(web-shell): add a daemon status page backed by GET /daemon/status Surface the consolidated daemon status API (#5174) in the Web Shell as a dashboard dialog opened from a sidebar footer button. - @qwen-code/sdk: DaemonClient.daemonStatus(detail) plus DaemonStatusReport* wire types for the /daemon/status envelope (summary and full detail). - @qwen-code/webui: loadDaemonStatus workspace action and a useDaemonStatusReport hook (exported as useDaemonStatus from daemon-react-sdk). - web-shell: DaemonStatusDialog rendering one dashboard — overall status badge, issues list, daemon/runtime/transport/security/limits/capabilities cards, plus per-session, workspace-diagnostics, and auth sections. The daemon's summary/full cost split is hidden from the operator rather than exposed as a toggle: the cheap summary rides a 5s auto-refresh while the expensive full report (which may spawn the ACP child and aggregate workspace diagnostics) is fetched only on open and on manual refresh, so parking the dialog open never rehits that path. Capabilities are sorted, counted, and height-capped; the long workspace path stays on one line, front-truncated so the tail remains visible. New pulse-icon sidebar entry; EN/zh-CN strings. - vite dev proxy: forward /daemon to the daemon; without it the SPA fallback answered /daemon/status with index.html and the dialog failed JSON parsing under npm run dev:daemon. * fix(web-shell): address review on the daemon status dashboard - Drive the status badge and issues list off the full report when it is available, not the summary. The daemon only rolls workspace/preflight/MCP problems into status+issues for detail=full, so the summary can read "ok" with no issues while a loaded full report is degraded — the dashboard now reflects the full rollup (live counters still come from the summary). - Guard the 5s poll with an in-flight ref so a slow/degraded daemon cannot accumulate overlapping status calls (useDaemonResource discards stale completions but does not abort; the client timeout is 30s). - Fix the public DaemonStatusReport wire type: runtime.channelWorker.channels is string[] (ChannelWorkerSnapshot), not an array of objects; mirror the remaining optional snapshot fields. * fix(web-shell): translate workspace section status badges WorkspaceSectionRow rendered the raw wire status (`ok`/`warning`/`error`/ `unavailable`) while every other badge in the dialog goes through `t()`, so under a Chinese UI these badges showed lowercase English. Route the badge through `t('daemon.level.<status>')` and add the missing `daemon.level.unavailable` key to both dictionaries. * fix(web-shell): scope toolbar error to summary; broaden dashboard test coverage - The toolbar "failed to load" banner now keys on the summary fetch only. A failed full fetch is already surfaced in the diagnostics section, so it no longer makes an otherwise-healthy summary (fresh cards + timestamp) read as broken. - Use the ASCII "..." ellipsis in the diagnostics-loading string to match the rest of the i18n dictionary. - Add tests: summary-healthy/full-failed degraded state, the ACP-disabled transport branch, uptime/memory/duration formatting across unit boundaries (day, GB, sub-second, fractional-second), and sidebar Daemon Status button click (expanded + collapsed) — the feature's only entry point. * fix(web-shell): pause polling on hidden tab; scope dev proxy; fix test mock - Skip the 5s status poll while document.hidden, matching the sidebar poll — a backgrounded tab no longer hits the daemon every 5s. - Narrow the vite dev proxy to the exact /daemon/status route instead of a bare /daemon prefix, mirroring the scoped /voice/stream entry; verified the dashboard still proxies (summary + detail=full) in dev. - Add a message field to the DaemonStatusReport issue mock in the webui provider test so it matches the required DaemonStatusReportIssue shape. * feat(web-shell): surface runtime/channel-worker diagnostics; a11y + polish Address the daemon-status review round: - Render the runtime startup/failure state (runtime.loading / runtime.error) in the Runtime card so the plausible-looking zero counters during startup are not mistaken for a healthy idle daemon. - Surface channel-worker diagnostics (state, exit code/signal, error, restart count) when the worker is enabled — these fields were fetched and typed but never shown, leaving a bare "down" with no context. - Include full.error.message in the diagnostics-failure line (matching the summary error path) so a failed detail fetch is actionable. - Show "N/A" instead of a literal "null" chip for null workspace summary values (the wire type allows null). - Add role="status" + aria-label to the health badge for screen readers. - Rename the public hook alias useDaemonStatus -> useStatusReport, matching the Daemon-prefix-stripping convention of the other re-exports. - Add tests: runtime startup/failure, channel-worker diagnostics, and the empty/disabled placeholders (sessions, rate limit, capabilities, ACP), toolbar-banner-with-data, and pure-loading branches. * fix(web-shell): contain daemon status crashes; workspace empty-state - Wrap the dashboard in a local ErrorBoundary so a malformed/partial daemon response (e.g. an older daemon omitting an additive field like channelWorker) — most likely exactly when the daemon is sick and the dashboard is most needed — shows a contained fallback instead of throwing to the root boundary and white-screening the whole web shell. - Add an empty-state to the Workspace Diagnostics card (parity with the Sessions card) for when full.workspace is empty. - Tests: error-boundary containment on a malformed report, and the workspace empty-state. * fix(web-shell): fix error-boundary recovery; contain detail crashes Address the review round (one Critical): - ErrorBoundary recovery was broken: the comment claimed resetKeys cleared the fallback, but none was passed. Switch to a function-form fallback that surfaces the actual render error (distinct from a network failure) and fix the comment — recovery happens on re-open, since the parent only mounts the dialog while open. - Wrap FullDetail in its own ErrorBoundary so a malformed detail=full payload is contained to the detail region instead of taking down the healthy summary cards with it; add a catch-all branch so a fetch that resolves without a `full` section shows a failed state instead of hanging on "Loading...". - Toolbar failure banner now shows only when the summary errored AND still has data on screen (`summary.error && summary.report`), so it no longer misrepresents a dashboard that is rendering from the full fallback. - SDK type: drop `& Record<string, unknown>` on DaemonStatusReport.daemon and add the typed optional `startup` field, matching the DaemonCapabilities convention the interface JSDoc claims. - Add a real useDaemonStatusReport hook test asserting the `report` alias maps from `data` — the dialog test mocks the whole hook, so nothing else guarded it. * feat(web-shell): surface runtime.activity in the daemon status dashboard PR #6270 added a runtime.activity sub-object to GET /daemon/status (activePrompts, lastActivityAt, idleSinceMs). Type it as an additive optional on the SDK DaemonStatusReport and render it in the Runtime card: active-prompt count and an idle duration ("no activity yet" when the daemon has seen none). Gated on the field's presence so older daemons that omit it still render. Verified end-to-end against a real qwen serve --web that emits the field. * fix(web-shell): daemon status polish — i18n count, negative clamp, coverage Address the review round (all minor): - Move the capabilities count into the i18n string (daemon.capabilities.titleCount with a {count} placeholder) so locales can reorder it. - Clamp negative durations in formatDurationMs (clock-skew defense). - Re-export the hook options type as StatusReportOptions for consumers wrapping useStatusReport. - Tests: use the real rate-limit tier keys (prompt/mutation/read) in the fixture, and cover the session-id display fallback, the channel-worker signal branch, and a healthy workspace section's chip/status rendering. * feat(web-shell): name the failing checks behind a workspace section status A "warning"/"error" workspace-diagnostics section only showed a rollup badge plus count chips, so e.g. a warning preflight was opaque — the operator couldn't tell it was the auth check without curling the API. Extract the individual warning/error cells from the section's raw data (across cells / servers / skills / tools / providers / hooks / extensions) and render each with its label and message (e.g. "auth: No auth method configured."). OK and other non-problem cells stay hidden. Verified end-to-end: a real daemon with no credentials now shows the auth warning inline under preflight. |