feat(cli): Add daemon status API#5174
Conversation
Co-authored-by: Qwen-Coder <[email protected]>
|
Qwen Code review did not complete successfully: Qwen review aborted with an API error before posting comments. See workflow logs. |
DragonnZhang
left a comment
There was a problem hiding this comment.
Review Summary
This PR adds a well-designed read-only GET /daemon/status endpoint with summary and full detail levels. The API design is sound, with good separation between in-memory daemon state and live workspace diagnostics.
However, there is a critical bug that needs to be fixed before merge:
Critical Issue
visitStatusContainers at line 518 only visits 6 array keys (cells, errors, servers, budgets, skills, providers), but summarizeStatusData at line 435 counts 9 array keys (it also includes tools, hooks, and extensions). This inconsistency means:
collectStatuses()won't catch status issues intools,hooks, orextensionssectionsinspectBudgetContainers()won't detect budget problems in those sections- The summary counts will show these sections exist, but their status won't be validated
The visitStatusContainers function should visit all 9 array keys to be consistent with summarizeStatusData.
for (const key of [
'cells',
'errors',
'servers',
'budgets',
'skills',
'tools',
'providers',
'hooks',
'extensions',
]) {
const value = data[key];
if (!Array.isArray(value)) continue;
for (const item of value) visitStatusContainers(item, visit);
}
Other Observations
- The API design is clean and well-documented
- The separation of
summary(in-memory only) vsfull(live diagnostics) is a good design choice - Security considerations are properly addressed (no sensitive values exposed)
- Test coverage is comprehensive
Verification
- CI: Failing on
review-prcheck - Presubmit: Downgrade to COMMENT required due to CI failure
Assessment
The implementation is solid, but the visitStatusContainers inconsistency is a critical bug that will cause status validation to miss issues in tools, hooks, and extensions sections. This needs to be fixed before merge.
Verdict: COMMENT (downgraded from APPROVE due to critical bug + CI failure)
— claude-opus-4-6 via Qwen Code /review
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
|
Qwen Code review did not complete successfully: Qwen review aborted with an API error before posting comments. See workflow logs. |
|
Qwen Code review did not complete successfully: Qwen review aborted with an API error before posting comments. See workflow logs. |
1 similar comment
|
Qwen Code review did not complete successfully: Qwen review aborted with an API error before posting comments. See workflow logs. |
qqqys
left a comment
There was a problem hiding this comment.
Prior critical daemon status rollup feedback is resolved in the latest head: tools, hooks, and extensions are now included in status traversal, with regression coverage. I did not find a remaining critical blocker in this pass.
|
Qwen Code review did not complete successfully: Qwen review aborted with an API error before posting comments. See workflow logs. |
Co-authored-by: Qwen-Coder <[email protected]>
|
Qwen Code review did not complete successfully: Qwen review aborted with an API error before posting comments. See workflow logs. |
✅ Local verification report — real
|
| Gate | Result |
|---|---|
npm install + full npm run build |
✅ rc=0 |
typecheck — @qwen-code/acp-bridge |
✅ rc=0 |
typecheck — @qwen-code/qwen-code (cli) |
✅ rc=0 |
eslint on all 8 changed source files |
✅ rc=0 |
merge-tree vs fresh origin/main |
✅ 0 conflicts |
2. Tests (vitest)
| Suite | Result |
|---|---|
serve/daemonStatus.test.ts |
✅ 4/4 |
serve/acpHttp/connectionRegistry.test.ts |
✅ 2/2 |
server.test.ts -t "GET /daemon/status" |
✅ 4/4 |
server.test.ts -t "serve capability registry" |
✅ 9/9 |
full server.test.ts (regression for fakeBridge signature change) |
✅ 433/433 |
full serve/acpHttp/ (regression for new kind discriminator) |
✅ 127/127 |
3. Runtime E2E against a live daemon
Started real daemons (node packages/cli/dist/index.js serve …) in tmux and exercised the endpoint with curl. To prove the endpoint itself does not start the ACP child, preheat was disabled (VITEST_WORKER_ID=1) so the only thing that could spawn a child was the request.
3.1 detail=summary (default) — ✅ matches docs exactly
- Top-level keys exactly
{v, detail, generatedAt, status, issues, daemon, security, limits, capabilities, runtime}— nofull. daemonomitslogPath(as documented);v=1,detail="summary",status="ok",issues=[].limits:maxSessions=20,listenerMaxConnections=256,acpConnectionCap=64— both caps surfaced and distinct.- The ACP child was NOT spawned —
pgrep -P <daemon-pid>returned 0 children before and after the request.
3.2 detail=full — ✅ all sections, fast, degrades independently
- Adds
full.{sessions, acpConnections, auth, workspace};workspacehas all 8 sections (mcp, skills, tools, providers, env, preflight, hooks, extensions). - Completed in 68 ms with each section well under the 1 000 ms timeout; on an idle daemon
fullalso did not spawn the child (idle fallback). daemon.logPathis present infullonly (authenticated-operator view) — consistent with the docs.- Production-like run (preheat ON): the ACP child (
node … --acp) spawned,runtime.channel.livebecametruein ~2 s, andfullthen returnedstatus:"ok"with all 8 workspace sectionsinitialized:true— confirming the live-child path, not just idle fallback.
3.3 Invalid detail → 400 invalid_detail — ✅ (and robust)
?detail=verbose → 400 {"code":"invalid_detail"}
?detail= → 400 (empty)
?detail=SUMMARY → 400 (case-sensitive, strict)
?detail=summary&detail=full → 400 (repeated param / array — no crash)
3.4 Bearer auth on loopback — ✅ the headline security claim holds
With --token configured, /daemon/status is gated even on loopback (registered after bearerAuth), unlike /health’s loopback exemption:
| Request (loopback, token set) | Code |
|---|---|
GET /daemon/status (no auth) |
401 |
GET /daemon/status (Authorization: Bearer …) |
200 |
GET /health (no auth) — contrast |
200 (loopback-exempt) |
GET /capabilities (no auth) |
401 |
3.5 Sensitive-value redaction — ✅
- Real ACP connection
4b29997c-1312-4df4-aaf7-d15608422f14appeared infull.acpConnections[0]asconnectionIdPrefix: "4b29997c"(8 chars). The full id is absent from the entire response. - With a token configured, the token value never appears in the response (only
security.tokenConfigured: true). - The
envsection reports env-var presence only (present: true/false) — API-key values are never emitted.
3.6 Issue rollup with real data — ✅
- Enabled
--rate-limit --rate-limit-read 3, hammered a read endpoint (3×200, 5×429)./daemon/statusthen reportedstatus:"warning", an issue{code:"rate_limit_hits", severity:"warning"}, andruntime.rateLimit.rejectedSinceStart.read: 5— exactly matching the 5 rejections.
3.7 Capability-registry refactor is behavior-preserving — ✅
currentServeFeatures()was extracted and shared by/capabilitiesand/daemon/status./capabilities.featuresis byte-identical tostatus.capabilities.features(61 features), anddaemon_statusis advertised.
Observations (non-blocking — for awareness, not change requests)
/daemon/statusis subject to thereadrate-limit tier (120/min) when--rate-limitis on, whereas/healthis exempt. This is deliberate and documented, but operators pointing a high-frequency dashboard at it should size--rate-limit-readaccordingly (a 0.5 s poll = 120/min, exactly the default cap).acpConnectionCap(64, ACP registry) andlistenerMaxConnections(256, TCP socket cap) are distinct limits — both are correctly surfaced underlimits; just noting they are not the same number by design.- The "summary does not start the ACP child" guarantee is endpoint-level. In a normal deployment the daemon preheats the child at startup independently of this route, so
channel.live/workspace sections populate on their own; I isolated the endpoint by disabling preheat.
Verdict: builds, type-checks, lints, and the full serve test suites are green; every documented runtime behavior (summary/full shapes, 400 invalid_detail, loopback bearer gating, id/token redaction, issue rollup) reproduces on a real daemon. LGTM from a verification standpoint. ✅
🇨🇳 中文版本(点击展开)
✅ 本地验证报告 — 真实 qwen serve 运行时(Linux)
在 Linux 上验证了 PR head 5efa94644(PR 表格中 Linux 标记为 未测试)。我新建了独立 worktree,跑了全量 + 定向测试套件,并用 curl 端到端驱动了一个真实的 qwen serve 守护进程。测试计划中的所有结论均可复现,未发现 bug,是一个可放心合并的候选。
环境: Node v22.22.2、npm 10.9.7、Linux 6.12.63 x64。merge-base 为 a76183f9c;PR 落后 origin/main 7 个 commit(3408c3211),但可干净合并(0 冲突),且 PR 改动的源文件在 main 上均无漂移。
1. 静态门禁
npm install+ 全量npm run build:✅ rc=0typecheck(acp-bridge / cli):✅ rc=0 / rc=0- 对全部 8 个改动源文件
eslint:✅ rc=0 - 对最新
origin/main做 merge-tree:✅ 0 冲突
2. 测试(vitest)
daemonStatus.test.ts:✅ 4/4acpHttp/connectionRegistry.test.ts:✅ 2/2server.test.ts -t "GET /daemon/status":✅ 4/4server.test.ts -t "serve capability registry":✅ 9/9- 全量
server.test.ts(回归fakeBridge签名改动):✅ 433/433 - 全量
acpHttp/(回归新增kind判别字段):✅ 127/127
3. 真实守护进程端到端
用 tmux 启动真实 daemon,用 curl 驱动。为证明端点本身不会启动 ACP 子进程,关闭了 preheat(VITEST_WORKER_ID=1),使唯一可能拉起子进程的就是请求本身。
- 3.1
detail=summary(默认)✅:顶层字段与文档完全一致,无full;daemon不含logPath;status=ok、issues=[];limits中maxSessions=20、listenerMaxConnections=256、acpConnectionCap=64。请求前后pgrep -P <pid>子进程数均为 0 → summary 没有启动 ACP 子进程。 - 3.2
detail=full✅:新增full.{sessions, acpConnections, auth, workspace},workspace含全部 8 个 section;68ms 完成,远低于 1000ms 超时;空闲时同样不拉起子进程(idle 降级);logPath仅在full出现。preheat 开启的拟生产运行:ACP 子进程(node … --acp)成功拉起,~2s 后channel.live=true,full返回status:ok且 8 个 section 全部initialized:true→ 实时子进程路径也验证通过。 - 3.3 非法
detail→400 invalid_detail✅:verbose/ 空值 /SUMMARY(大小写敏感)/ 重复参数 全部返回 400,且不崩溃。 - 3.4 loopback bearer 鉴权 ✅(核心安全点):配置
--token后,即使在 loopback,/daemon/status无 token → 401,带 token → 200;对照/health无 token → 200(loopback 豁免);/capabilities无 token → 401。证明 status 路由注册在bearerAuth之后,不享受/health的豁免。 - 3.5 敏感值脱敏 ✅:真实连接
4b29997c-…在响应中只暴露 8 位前缀connectionIdPrefix: "4b29997c",完整 id 不出现;配置 token 后响应中不含 token 值(仅tokenConfigured:true);envsection 仅报告变量是否存在,不输出值。 - 3.6 issue 汇总(真实数据)✅:开启
--rate-limit --rate-limit-read 3并打满(3×200、5×429)后,/daemon/status返回status:"warning"、issuerate_limit_hits、rejectedSinceStart.read:5,与 5 次 429 完全吻合。 - 3.7 capability 重构等价 ✅:抽出的
currentServeFeatures()被/capabilities与/daemon/status共用,两者features(61 项)逐字节一致,且包含daemon_status。
观察项(非阻塞,仅供参考)
- 开启
--rate-limit时,/daemon/status受read限速(120/min)约束,而/health豁免——这是有意且已写入文档的设计,但若用高频 dashboard 轮询该端点,应相应调大--rate-limit-read(0.5s 一次即 120/min,正好等于默认上限)。 acpConnectionCap(64,ACP 注册表)与listenerMaxConnections(256,TCP socket)是两个不同的上限,limits中均正确暴露,二者本就不应相等。- “summary 不启动子进程”是端点级保证;正常部署中 daemon 会在启动时独立 preheat 子进程,因此
channel.live/workspace section 会自行 populate,我通过关闭 preheat 来隔离验证端点行为。
结论: 构建 / 类型检查 / lint / 全量 serve 测试套件全绿;所有文档化的运行时行为(summary/full 结构、400 invalid_detail、loopback bearer 鉴权、连接 id / token 脱敏、issue 汇总)均在真实 daemon 上复现。从验证角度 LGTM ✅。
Verification methodology: isolated git worktree, own npm install/build, real qwen serve daemons driven over HTTP (no mocked transport); ACP connection minted via the real POST /acp initialize handshake.
|
@qwen-code /triage |
|
Thanks for the PR, @doudouOUC! Template looks good ✓ On direction: This is well-aligned. Operators troubleshooting stuck sessions, pending permissions, or transport fan-out currently have to piece together state from multiple narrower endpoints or logs — a consolidated status API is a natural fit. Claude Code has On approach: The two-tier design (summary = cheap in-memory, full = workspace queries with independent degradation) is pragmatic and gets the 80/20 right — most troubleshooting starts with summary. The One thing worth splitting out: the The Overall the scope feels right — 15 files, but each one is a focused change for the stated goal. Moving on to code review. 🔍 中文说明感谢贡献,@doudouOUC! 模板完整 ✓ 方向: 对齐。排查 stuck session、pending permission、transport fan-out 时,operator 目前需要从多个窄接口或日志拼凑状态,统一的 status API 是自然选择。Claude Code 有 方案: 两级设计(summary = 低成本内存读取,full = workspace 查询并独立降级)务实,80/20 分得合理——大部分排查从 summary 开始。server.ts 的 有一点值得拆出来:
整体范围合理——15 个文件,但每个都是聚焦于目标的改动。进入代码审查 🔍 — Qwen Code · qwen3.7-max |
|
Qwen Code review did not complete successfully: Qwen review aborted with an API error before posting comments. See workflow logs. |
Code ReviewIndependent proposal before reading the diff: add a The PR's approach matches this almost exactly. A few things worth noting:
No critical blockers. The Real-Scenario TestingStarted the daemon built from this PR's branch on port 4179 with a fresh workspace, then hit all three endpoint modes. Server startupGET /daemon/status (summary — default)Summary correctly omits GET /daemon/status?detail=fullFull mode correctly includes GET /daemon/status?detail=verbose (invalid)Returns 400 with correct error code. ✓ Unit testsAll 36 relevant tests pass. 中文说明代码审查独立方案:在 server.ts 添加路由,创建独立的 PR 方案与独立方案基本一致。 实际测试从 PR 分支构建 daemon,在 4179 端口启动后测试三种模式:
36 个相关单测全部通过。 — Qwen Code · qwen3.7-max |
|
Stepping back: this PR does one thing — gives operators a single endpoint to ask "how is the daemon doing?" — and does it well. The two-tier design (summary for dashboards, full for active troubleshooting) is the right call. The code is straightforward, the security posture is correct, and the real-scenario testing confirms everything the PR promised. My independent proposal matched the PR's approach almost exactly, and I didn't find a simpler path it missed. The only minor reservation is the Ship it. ✅ 中文说明这个 PR 做了一件事:给 operator 一个统一入口问"daemon 状态如何",做得很好。两级设计(summary 给 dashboard,full 给主动排查)是正确的选择。代码清晰,安全姿态正确,实际测试确认了 PR 的所有承诺。 唯一的小保留是测试文件中捆绑的 可以合并。✅ — 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 review did not complete successfully: Qwen review aborted with an API error before posting comments. See workflow logs. |
…n PRs (#5211) * fix(e2e): add daemon_status to serve capabilities baseline; run E2E on PRs The `qwen serve — capabilities envelope` integration test hard-codes the expected advertised-feature list. PR #5174 added the `daemon_status` capability to SERVE_CAPABILITY_REGISTRY (and the server.test.ts unit baseline) but did not update this integration test, so the list drifted by one entry and the E2E job began failing on main. Root cause it slipped through: E2E only triggered on push to main / merge_group, never on PRs, so this stale assertion was never exercised before merge. Add a pull_request trigger (targeting main) so capability/ contract regressions surface at PR time, and key concurrency on the PR number so superseded PR runs cancel. * fix(e2e): guard fork PRs from secretless runs; dedupe push/PR triggers Addresses review feedback on #5211: - Skip the e2e matrix on fork PRs. Forks have no access to repository secrets (OPENAI_*, DOCKERHUB_*), so without a guard every fork PR produces 3 guaranteed red jobs and wastes CI minutes. A head-repo guard runs the jobs for same-repo PRs (and push / merge_group) but skips them for forks. - Key concurrency on the head ref (github.head_ref || github.ref_name) so a push to a feat/e2e/** branch and a pull_request from that same branch share one group and cancel each other, instead of running the full matrix twice for the same change. Keeps the feat/e2e/** push trigger (the no-PR e2e iteration escape hatch) intact. - Stop cancelling in-progress main runs so every main commit produces a complete e2e result (matching ci.yml's policy and the very signal that surfaced this bug); still cancel superseded PR / feature-branch runs. --------- Co-authored-by: jinye <[email protected]>
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 a read-only
GET /daemon/statusendpoint forqwen servewithsummaryandfulldetail levels. The summary response reports daemon runtime state from in-memory counters, including session counts, permission pressure, REST SSE and ACP transport counts, rate-limit rejects, process memory, resolved limits, auth posture, and advertised capabilities. The full response adds per-session diagnostics, ACP connection diagnostics, auth device-flow counts, and independently degraded workspace status sections for MCP, skills, tools, providers, env, preflight, hooks, and extensions.The change also exposes a
daemon_statuscapability tag, adds bridge and ACP registry snapshot helpers, keeps sensitive values out of the status response, and documents the new endpoint for operators and protocol clients.Why it's needed
Operators currently have to query several narrower endpoints or infer daemon state from logs when troubleshooting issues like stuck sessions, pending permissions, transport fan-out, MCP budget pressure, preflight failures, or rate-limit rejects. A consolidated status API gives dashboards and support tooling one stable JSON surface for quick triage without spawning sessions or starting the ACP child for the default summary view.
Reviewer Test Plan
How to verify
Run
qwen serveand requestGET /daemon/status; the default response should havedetail: "summary",v: 1, daemon/security/limits/runtime/capabilities sections, and nofullsection. RequestGET /daemon/status?detail=full; the response should includefull.sessions,full.acpConnections,full.auth, andfull.workspacesections. Request an unsupported detail value such asGET /daemon/status?detail=verbose; the response should be400withcode: "invalid_detail". When a bearer token is configured, the status route should requireAuthorization: Bearer ...like other normal daemon APIs rather than using the/healthloopback exemption.Local validation ran:
npm run lint,npm run build -- --cli-only,npm run typecheck,cd packages/cli && npx vitest run src/serve/server.test.ts -t "GET /daemon/status",cd packages/cli && npx vitest run src/serve/acpHttp/connectionRegistry.test.ts, andcd packages/cli && npx vitest run src/serve/server.test.ts -t "serve capability registry".Evidence (Before & After)
N/A; this is a JSON API and documentation change, not a TUI or visual UI change.
Tested on
Environment (optional)
Node v22.22.3, npm 10.9.8, Darwin 25.4.0 arm64.
Risk & Scope
Linked Issues
N/A
中文说明
What this PR does
新增只读的
GET /daemon/status接口,支持summary和full两种 detail。summary 响应只从内存计数读取 daemon 运行态,包括 session 数、permission 压力、REST SSE 与 ACP transport 计数、rate-limit 拒绝数、进程内存、已解析 limits、鉴权姿态和已发布 capabilities。full 响应额外包含 session 诊断、ACP 连接诊断、auth device-flow 数量,以及 MCP、skills、tools、providers、env、preflight、hooks、extensions 等 workspace status sections,并且每个 section 独立降级。这次改动还暴露
daemon_statuscapability tag,新增 bridge 与 ACP registry 的 snapshot helper,避免在 status 响应中泄露敏感值,并为 operator 和 protocol client 补充新接口文档。Why it's needed
排查 stuck session、pending permission、transport fan-out、MCP budget 压力、preflight 失败或 rate-limit 拒绝等问题时,operator 目前需要查询多个更窄的接口,或者从日志里推断 daemon 状态。统一的 status API 给 dashboard 和支持工具提供一个稳定的 JSON 入口,便于快速定位问题;默认 summary 视图不会 spawn session,也不会启动 ACP child。
Reviewer Test Plan
How to verify
运行
qwen serve后请求GET /daemon/status;默认响应应包含detail: "summary"、v: 1、daemon/security/limits/runtime/capabilities sections,并且没有fullsection。请求GET /daemon/status?detail=full;响应应包含full.sessions、full.acpConnections、full.auth和full.workspacesections。请求不支持的 detail,例如GET /daemon/status?detail=verbose;响应应为400,并包含code: "invalid_detail"。配置 bearer token 时,status 路由应像其他普通 daemon API 一样要求Authorization: Bearer ...,而不是使用/health的 loopback 免鉴权例外。本地验证已运行:
npm run lint、npm run build -- --cli-only、npm run typecheck、cd packages/cli && npx vitest run src/serve/server.test.ts -t "GET /daemon/status"、cd packages/cli && npx vitest run src/serve/acpHttp/connectionRegistry.test.ts、cd packages/cli && npx vitest run src/serve/server.test.ts -t "serve capability registry"。Evidence (Before & After)
N/A;这是 JSON API 和文档改动,不是 TUI 或可视 UI 改动。
Tested on
Environment (optional)
Node v22.22.3,npm 10.9.8,Darwin 25.4.0 arm64。
Risk & Scope
Linked Issues
N/A