feat(serve): Add runtime.activity fields to daemon status API - #6270
Conversation
There was a problem hiding this comment.
Pull request overview
Adds daemon-wide activity diagnostics to the GET /daemon/status JSON response so operators and dashboards can see whether prompts are currently running and when the daemon was last active.
Changes:
- Add
runtime.activity(activePrompts,lastActivityAt,idleSinceMs) to the daemon status response and its TypeScript shape. - Populate the new fields from existing bridge getters and add unit tests for both active and never-active states.
- Update the serve protocol developer docs to document the new response fields.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| packages/cli/src/serve/run-qwen-serve.ts | Extends the bootstrap /daemon/status placeholder payload to include the new runtime.activity fields. |
| packages/cli/src/serve/daemon-status.ts | Adds runtime.activity to the status response type and populates it from bridge activity getters. |
| packages/cli/src/serve/daemon-status.test.ts | Adds coverage for the new runtime.activity fields (active + never-active cases). |
| docs/developers/qwen-serve-protocol.md | Documents runtime.activity in the /daemon/status response shape and semantics. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Thanks for the PR! Re-running triage after the CI fix. Template looks good ✓ — all sections present, bilingual, test plan clear. Problem: This is a Direction: Solid. Exposing activity metrics on the richer status endpoint consolidates data that was previously split across two endpoints. No Claude Code CHANGELOG reference for this area, but the observability angle is clearly relevant to qwen-code's serve infrastructure. Approach: Minimal and focused. Three new fields reading from existing bridge getters, CI is now all green (7 successful, 14 skipped, 0 failing) after the 中文说明感谢贡献!CI 修复后重新跑 triage。 模板完整 ✓ — 所有章节齐全,双语,测试计划清晰。 问题:这是 方向:可靠。在更丰富的 status 端点上暴露活动指标,整合之前分散在两个端点的数据。Claude Code CHANGELOG 无此领域的参考,但可观测性角度与 qwen-code serve 基础设施明确相关。 方案:最小化且聚焦。三个新字段读取已有 bridge getter, CI 修复后全绿(7 成功,14 跳过,0 失败)。进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewRe-review after the CI fix ( The The MCP server health summary ( No correctness bugs, no security concerns, no reuse violations. 14 daemon-status tests pass (including MCP summary + 2 activity tests), 122 run-qwen-serve tests pass (the 4 previously-failing tests now green). Test ResultsReal-Scenario TestingStarted Daemon startup
|
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
|
Re-run after the CI fix. Everything that was flagged in the first pass is now resolved. The Independent baseline comparison: I would have done exactly what this PR does — read from the existing bridge getters, add a The live curl confirms the response shape: Two small features in one PR (activity + MCP summary), but both are under 50 lines of logic each and both target the same endpoint. Not worth splitting. Approving. ✅ 中文说明CI 修复后重新跑。第一轮审查中标记的问题已全部解决。
独立基线对比:我会做的和本 PR 完全一致——读取已有 bridge getter,添加 实际 curl 确认了响应格式:pre-activity 状态下 一个 PR 包含两个小功能(活动 + MCP 摘要),但两者逻辑各不超过 50 行且都针对同一端点。不值得拆分。 批准 ✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Downgraded from Approve to Comment: CI still running. All findings are suggestions — no blockers.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI still running.
— qwen3.7-max via Qwen Code /review
|
@qwen-code /resolve |
Add activePrompts, lastActivityAt, and idleSinceMs to the GET /daemon/status runtime section. These fields already exist on the bridge (and are exposed via GET /health?deep=1) but were missing from the richer status endpoint that operators use for troubleshooting. The idleSinceMs value is computed from a cached lastActivityAt read (same pattern as the health handler) to ensure consistency within a single response.
Extract serversConnected, serversErrored, and serversDisabled counts from the MCP servers array into the workspace.mcp.summary object. Operators can see MCP fleet health at a glance without expanding the full JSON.
dcc0011 to
c7561e5
Compare
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
|
Qwen Code resolved the merge conflicts, but the head branch changed while resolving, so the update was not pushed. Re-run /resolve. The resolved diff is attached as the Merge Conflict Resolution Summary — PR #6270Branch: Root CauseTwo independent additive features touched the same files on divergent branches:
Both are purely additive and orthogonal — the correct resolution in every case is to keep both sides. Conflicts Resolved (5 regions across 3 files)1.
|
CI failure root cause —
|
Add ?? null / ?? 0 fallbacks for lastActivityAt and activePromptCount to prevent RangeError when a test fake bridge omits these properties.
|
Thanks — agreed, great catch. Fixed in 6db7909 with |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI failing: Test (ubuntu-latest, Node 22.x).
— qwen3.7-max via Qwen Code /review
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
PR QwenLM#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.
QwenLM#6272) * feat(web-shell): add a daemon status page backed by GET /daemon/status Surface the consolidated daemon status API (QwenLM#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 QwenLM#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.
What this PR does
Adds three activity-tracking fields to the
GET /daemon/statusresponse under a newruntime.activitysub-object:activePrompts(number of sessions with an in-flight prompt),lastActivityAt(ISO 8601 timestamp of last prompt/session activity), andidleSinceMs(milliseconds since last activity). All three values come from existing bridge getters that were already used byGET /health?deep=1but were absent from the richer status endpoint.Why it's needed
Operators troubleshooting daemon issues need to know whether the daemon is actively processing prompts, when it last had activity, and how long it has been idle. This data was only available through the lightweight
/health?deep=1probe, which is designed for liveness checks rather than diagnostic dashboards. Adding it to/daemon/statusgives status-page consumers and the planned daemon dashboard a single endpoint for all runtime diagnostics.Reviewer Test Plan
How to verify
Run
qwen serve, thencurl http://127.0.0.1:4170/daemon/status | jq '.runtime.activity'. Before any session activity, the response should show{"activePrompts":0,"lastActivityAt":null,"idleSinceMs":null}. After sending a prompt,lastActivityAtshould be a recent ISO timestamp andidleSinceMsshould reflect the elapsed time. During an active prompt,activePromptsshould be>0.Tests:
cd packages/cli && npx vitest run src/serve/daemon-status.test.ts(11 tests, including 2 new activity-field tests covering both active and never-active states).Evidence (Before & After)
N/A — this is a JSON API addition, not a visual change.
Tested on
Environment (optional)
Node v24.12.0, Darwin 25.4.0 arm64.
Risk & Scope
idleSinceMsis computed from a cachedlastActivityAtread (same pattern as/health?deep=1) to ensure consistency within a single response.runtime.activityfield is additive.Linked Issues
N/A
中文说明
这个 PR 做了什么
在
GET /daemon/status响应中新增runtime.activity子对象,包含三个活动跟踪字段:activePrompts(正在处理提示的会话数)、lastActivityAt(最后一次提示/会话活动的 ISO 8601 时间戳)和idleSinceMs(距离最后活动的毫秒数)。这三个值来自现有 bridge getter;这些 getter 之前已经被GET /health?deep=1使用,但更完整的 status 端点尚未暴露这些字段。为什么需要
排查 daemon 问题时,运维人员需要知道 daemon 是否正在处理提示、最后活跃时间,以及已经空闲了多久。这些数据之前只能通过轻量级的
/health?deep=1探针获取,而该探针主要用于存活检查,不适合作为诊断 dashboard 的数据来源。将这些字段加入/daemon/status后,状态页消费者和规划中的 daemon dashboard 可以通过单一端点获取完整的运行时诊断数据。Reviewer Test Plan
如何验证
运行
qwen serve,然后执行curl http://127.0.0.1:4170/daemon/status | jq '.runtime.activity'。在任何会话活动之前,响应应显示{"activePrompts":0,"lastActivityAt":null,"idleSinceMs":null}。发送提示后,lastActivityAt应为近期 ISO 时间戳,idleSinceMs应反映已经经过的时间。提示正在处理中时,activePrompts应为>0。测试:
cd packages/cli && npx vitest run src/serve/daemon-status.test.ts(11 个测试,包括 2 个新增活动字段测试,覆盖活跃状态和从未活跃状态)。Evidence (Before & After)
N/A — 这是 JSON API 增量变更,不涉及视觉变化。
Tested on
Environment (optional)
Node v24.12.0, Darwin 25.4.0 arm64.
Risk & Scope
idleSinceMs基于缓存的lastActivityAt读取结果计算(与/health?deep=1使用相同模式),以确保单次响应内的一致性。runtime.activity字段是增量新增。Linked Issues
N/A