Skip to content

feat(serve): add pollable daemon turn status - #9141

Closed
BenGuanRan wants to merge 5 commits into
QwenLM:mainfrom
BenGuanRan:feat/daemon-turn-status-polling-final
Closed

feat(serve): add pollable daemon turn status#9141
BenGuanRan wants to merge 5 commits into
QwenLM:mainfrom
BenGuanRan:feat/daemon-turn-status-polling-final

Conversation

@BenGuanRan

@BenGuanRan BenGuanRan commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Closed as an accidental duplicate of #9080. All maintained work continues on #9080.

BenGuanRan and others added 5 commits August 13, 2026 17:17
Add GET /session/:id/turns/current and GET /session/:id/turns/:promptId
so external callers can poll a turn's lifecycle state (queued / running /
completed / cancelled / error) and result instead of holding the SSE
stream for the whole turn lifetime.

- Live state comes from the bridge's pending prompt queue; settled
  outcomes from persisted turn_result transcript records, so results
  survive daemon restarts and the daemon keeps no per-turn memory
- Each prompt captures its own recording and settles exactly that one,
  so overlapping turns (DAEMON-003 deadline overlap) can never
  misattribute one turn's outcome to another promptId
- Enforces the same client authorization as POST /session/:id/prompt

Refs QwenLM#8680
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR — this is the third iteration of the turn-status work, and by far the slimmest.

Template: complete and bilingual ✓

Problem: real, not theoretical. This is the direct complement of #4585, which made POST /session/:id/prompt non-blocking (202 + promptId) — since then, submitting clients have had no bounded way to learn a turn's terminal state without holding the SSE stream open. The linked issue #8680 was triaged at issue stage and accepted for exploration (P2, background-automation), with a design-doc PR explicitly welcomed. This PR brings that design doc.

Direction: aligned — read-only, additive, and squarely on the background-automation integration surface. One thing to be clear-eyed about: it is an always-on public contract (two new GET routes plus a session_turn_status capability, no feature flag), which is part of why it needs maintainer sign-off rather than bot approval.

Size: cross-package core infrastructure (packages/core/src/services/**, acp-bridge, cli/serve + acp-integration). By our count that's ≈ 888 production-logic lines (additions + deletions), ≈ 2,230 lines of tests, ≈ 46 lines of docs. As a feat it is not size-blocked, but 500+ production lines in core triggers the maintainer-awareness escalation: this PR will not be auto-approved regardless of how clean the review is.

Approach: the scope finally feels right. Next to #8682 (≈7.8k lines) and #9080 (≈3k), this version drops the crash-journal and strict-persistence machinery and composes with what main already has: the bridge's exactly-once terminal publication, the shared response-capture path, the existing transcript reader, and the fork filter. Live queue state + 64-entry terminal overlay + a bounded 10-page transcript scan is about the minimal set of sources this feature needs. One housekeeping item: #9080 and #8682 are still open — if this supersedes them, please close them so review doesn't split across three PRs.

Risk: packages/cli/src/acp-integration/ matches this repo's revert-correlated high-risk paths, so this gets full-depth review, and CI evidence is required before anyone approves.

Moving on to code review. 🔍

中文说明

感谢贡献——这是 turn-status 功能的第三个迭代,也是范围收敛得最好的一次。

模板:完整且双语 ✓

问题:真实存在,不是理论假设。这是 #4585 的直接补全——#4585POST /session/:id/prompt 改成了非阻塞(202 + promptId),此后提交方客户端在不保持 SSE 长连接的情况下,没有任何有界方式查询 turn 的终态。关联 issue #8680 已在 issue 阶段完成评审并被接受探索(P2,background-automation),明确表示欢迎带设计文档的 PR。本 PR 附带了该设计文档。

方向:对齐——只读、纯增量,完全落在 background-automation 集成面上。需要清醒看待的一点:这是一个默认开启的公开契约(两个新 GET 路由 + session_turn_status capability,无 feature flag),这也是它需要维护者签核而非机器人直接批准的原因之一。

规模:跨包核心基础设施改动(packages/core/src/services/**acp-bridgecli/serve + acp-integration)。统计约为 888 行生产逻辑(增 + 删)、约 2,230 行测试、约 46 行文档。作为 feat 类型不按规模阻断,但核心路径 500+ 生产行触发维护者知会升级:无论 review 多干净,本 PR 都不会被自动批准。

方案:范围终于合理了。相比 #8682(约 7.8k 行)和 #9080(约 3k 行),本版本砍掉了 crash-journal 和 strict-persistence 机制,转而与 main 已有机制组合:bridge 的 exactly-once 终态发布、共享 response-capture 路径、现有 transcript reader、fork 过滤器。live 队列状态 + 64 条终态 overlay + 有界的 10 页 transcript 扫描,大约就是这个功能所需的最小数据源集合。一个待办事项:#9080#8682 仍然是 open 状态——如果本 PR 替代它们,请关闭它们,避免评审分散在三个 PR 上。

风险packages/cli/src/acp-integration/ 命中本仓库与 revert 相关的高风险路径,因此执行全深度 review,且批准前必须有 CI 证据。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at bbf24434c390c6ee1fd1856013937196ae0f60d1 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Code review

I read the full diff against main and found no critical blockers. The implementation composes with existing machinery instead of building parallel systems: the overlay hooks into the bridge's existing exactly-once publishPromptTerminal, the result-text capture reuses the shared response-block capture, persistence reuses the transcript reader, and the fork-identity leak is closed in forkSession itself. Boundedness is enforced everywhere (64-entry overlay, 10×500 scan window, 32 KiB text, 4 KiB/256 error fields) and the reader side validates payloads strictly (isTurnResultRecordPayload).

Two hunks looked suspicious at first and I verified both against main:

  • The promptDisplayText change in bridge.ts (channelDisplayTextpendingText) is behavior-preserving: pendingText equals channelDisplayText in every non-empty case; only the empty-display-text edge moves, aligning the recorded promptText with what listPendingPrompts reports ([image] placeholder for image-only prompts). The Session-side consumers of that meta key are unchanged.
  • The new Session.prompt() wrapper converts controlled aborts (user-cancel, new-prompt, session-dispose) into {stopReason:'cancelled'} at the outer level. That mirrors the convention already present inside the send loop on main (which covers the first and third reasons there), so it's an extension of an established pattern, not a new invention — and it's what lets cancelled/superseded admissions land as turn_result records.

Three behavior surfaces a maintainer should consciously sign off, all covered by the new tests:

  1. listPendingPrompts now also filters out terminalPublished entries — a subtle tightening of an existing endpoint.
  2. The outer prompt() abort conversion adds new-prompt to the controlled set at admission level (interplay with bridge-owned FIFO cancellation).
  3. The capability is always-on with no flag — deliberate per the design doc, but it's new public surface.

Authorization is correct as far as static review can tell: both routes require the live owning runtime, parse the client id at the route, and re-resolve trust in the bridge exactly like /prompt (there's a dedicated "rejects a foreign clientId" test). Recording only fires when an invocation context exists, so non-daemon prompts don't write turn_result.

The test suite is genuinely adversarial, not box-ticking: race paths (a terminal published while the persisted lookup is pending and while it rejects), rewind-clear races, the 64-entry cap, truncation flags, fork identity exclusion, and the exact tool-boundary semantics.

sequenceDiagram
    participant P1 as Daemon client
    participant P2 as serve route
    participant P3 as Bridge
    participant P4 as ACP child
    participant P5 as Transcript reader
    P1->>P2: GET session turns by promptId
    P2->>P3: getSessionTurnStatus - auth check
    P3->>P3: check live FIFO queue
    P3->>P4: control RPC turn_status
    P4->>P5: best-effort flush then bounded backward scan
    P5-->>P4: turn_result record or none
    P4-->>P3: persisted result
    P3->>P3: re-check live queue and terminal overlay
    P3-->>P2: merged status or idle
    P2-->>P1: 200 status or 404 prompt_not_found
Loading

The re-check after the awaited child read (including on the failure path) is what keeps a concurrent settle from regressing to stale data — that ordering is the heart of the design and it's implemented as described.

Files changed (26 of 26 shown)
File What changed
docs/design/daemon-turn-status-endpoint.md New design doc: state model, bounded sources, explicit non-goals
docs/developers/qwen-serve-protocol.md Protocol-doc paragraph for the two new routes
integration-tests/cli/qwen-serve-routes.test.ts Expects the new capability in the envelope
integration-tests/cli/qwen-serve-streaming.test.ts E2E: tool-boundary final answer, truncation code, reload lookup
packages/acp-bridge/src/bridge.test.ts 21 tests for getSessionTurnStatus incl. race and rewind paths
packages/acp-bridge/src/bridge.ts Live status, 64-entry terminal overlay, merge with child read, startedAt
packages/acp-bridge/src/bridgeTypes.ts BridgeTurnStatus shape; startedAt on pending entries
packages/acp-bridge/src/status.ts New control-extension method name
packages/cli/src/acp-integration/acpAgent.test.ts Child-handler tests incl. flush failure and invalid params
packages/cli/src/acp-integration/acpAgent.ts Control handler: best-effort flush plus bounded backward transcript scan
packages/cli/src/acp-integration/session/Session.test.ts turn_result recording across complete, cancel, and error paths
packages/cli/src/acp-integration/session/Session.ts Wraps prompt with turn recording; reuses response capture for final answer
packages/cli/src/serve/acp-session-bridge.ts Re-exports BridgeTurnStatus
packages/cli/src/serve/capabilities.ts Registers the session_turn_status capability
packages/cli/src/serve/routes/session.ts Two GET routes, live-runtime only, 404 prompt_not_found
packages/cli/src/serve/server.test.ts Route-level tests for 200, 404, and 400 paths
packages/cli/src/serve/server/telemetry-catalog.test.ts Route-attribution catalog expectation
packages/cli/src/serve/server/telemetry.test.ts Route-attribution test update
packages/cli/src/serve/server/telemetry.ts Attributes the two new routes
packages/core/src/services/chatRecordingService.test.ts Payload validation and error-normalization tests
packages/core/src/services/chatRecordingService.ts turn_result subtype, payload types, bounded best-effort append
packages/core/src/services/sessionService.test.ts Fork drops turn_result identities and relinks chained artifacts
packages/core/src/services/sessionService.ts Fork excludes turn_result records
packages/core/src/utils/conversation-branches.test.ts turn_result treated as neutral tail
packages/core/src/utils/conversation-branches.ts Adds turn_result to the neutral-tail set
packages/core/src/utils/transcript-records.ts Adds turn_result to known record subtypes

Test evidence — the PR's own CI

This is an unattended CI run, so per policy I did not build or execute any PR code; the evidence below is the PR's own CI read through the API at the reviewed commit. As of this pass the substantive suites are still running — no failures, no passes yet. The Qwen Triage Finalize job will rewrite the table below once CI settles. Not verified: anything requiring a live run (real-scenario behavior rests on the PR's CI and the sandboxed lanes, not on this review).

Final CI results for bbf2443 (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Classify PR ✅ success
Dependency CVE audit ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
macos-latest / Java 21 ✅ success
Real daemon E2E / Java 11 ✅ success
Secret scan (TruffleHog) ✅ success
Serve A/B (ubuntu-latest, Node 22.x) ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
ubuntu-latest / Java 11 ✅ success
ubuntu-latest / Java 17 ✅ success
ubuntu-latest / Java 21 ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
windows-latest / Java 21 ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

Sandboxed verification would settle the remaining behavioural claims: @qwen-code /verify — that the resultText tool-boundary semantics, the restart/reload lookup, and the overlay-vs-transcript precedence actually hold against the base build, since a suite can pass while pinning nothing. The author lacks write access, so this would be a sponsored run: a maintainer's @qwen-code /verify comment approves the head it is written against, and the run carries a pre-execution risk screen plus a full workspace wipe. The code under verification is still fork-authored input — read the resulting report with the same skepticism as the fork's own CI logs.

Real-scenario (tmux) testing: N/A for this pass — unattended CI never drives live product, and the change is daemon HTTP surface with no TUI component. A maintainer can trigger the isolated @qwen-code /tmux job if live coverage is wanted.

中文说明

代码审查:完整对照 main 阅读了 diff,未发现阻断性问题。实现与现有机制组合而非另起炉灶:overlay 挂在 bridge 既有的 exactly-once 终态发布上,结果文本复用共享 response-block 捕获,持久化复用 transcript reader,fork 身份泄漏在 forkSession 本身堵住。各处都有边界约束(64 条 overlay、10×500 扫描窗口、32 KiB 文本、4 KiB/256 错误字段),读取端用 isTurnResultRecordPayload 严格校验。

两个起初可疑的改动点都已在 main 上核实:bridge.tspromptDisplayText 的改动在非空场景下行为不变,仅空 display-text 边界对齐为 [image] 占位;Session.prompt() 外层包装把受控 abort 转成 cancelled,与 main 发送循环内已有的约定一致,是既有模式的延伸,并非新发明。

三处需要维护者有意识签核的行为面(均有新测试覆盖):listPendingPrompts 过滤收紧、admission 层新增 new-prompt 受控 abort 转换、capability 默认开启无开关。

鉴权在静态审查范围内是正确的:两个路由都要求 live owning runtime,路由层解析 client id,bridge 层像 /prompt 一样再次解析信任关系(有专门的"拒绝外部 clientId"测试)。无 invocation context 时不写 turn_result,非 daemon prompt 不受影响。

测试套件是有对抗性的:竞态路径(persisted 查询 pending 和 reject 时发布终态)、rewind 清空竞态、64 条上限、截断标志、fork 身份排除、精确的工具边界语义。

测试证据:本次为无人值守 CI 运行,按规则未构建或执行任何 PR 代码;以上证据来自 PR 自身 CI 在受审 commit 上的 API 读取。截至本次,主要套件仍在运行——尚无失败,也尚无通过。Finalize 任务会在 CI 落定后更新上方表格。未验证:需要真实运行的部分。

沙箱验证可以补齐剩余行为性结论:@qwen-code /verify——验证 resultText 工具边界语义、重启/重载查询、overlay 与 transcript 优先级相对 base build 确实成立。作者无写权限,因此为赞助运行:维护者评论触发,运行前有前置风险筛查与工作区清空;被验证代码仍是 fork 输入,请对其报告保持与 fork CI 日志同等的怀疑。

真实场景(tmux)测试:本次不适用——无人值守 CI 不驱动真实产品,且本改动为 daemon HTTP 面,无 TUI 成分。

Qwen Code · qwen3.8-max

Reviewed at bbf24434c390c6ee1fd1856013937196ae0f60d1 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — the static review is genuinely clean (no blockers found; this is well-constructed work), but the core-scale escalation caps this at 3/5: a maintainer needs to sign off before any approval, and CI on the reviewed commit hasn't landed yet.

Stepping back: my independent take before reading the diff was that this feature needs exactly three sources — live FIFO state only the bridge has, terminal outcomes bridging the transcript-visibility gap, and a bounded durable lookup for restarts — and that's precisely what this PR builds, each reusing an existing mechanism rather than adding a parallel one. The progression across the three iterations is the right direction: #8682 tried to make the result store durable under crashes (7.8k lines), #9080 trimmed it, and this version accepts an explicitly best-effort contract and documents its misses (prompt_not_found does not mean "never existed"). That honesty about the contract is the design decision that made the scope sane, and it's the thing a maintainer should confirm they're comfortable with — some consumers may want stronger guarantees later, and this PR deliberately refuses to provide them.

What gives me pause is not the code but the station it sits at: ~888 production lines across core services, acp-bridge, and the acp-integration high-risk paths, from a fork, adding always-on public surface with no flag. Every piece of that is individually justified — and every piece is exactly why the two-tier gate says a human must own the merge decision. The review found nothing to fix; it just isn't mine to finish.

Two follow-ups for the thread, neither blocking: please close #9080 and #8682 if this supersedes them, and the @qwen-code /verify line in the Stage 2 comment names what a sponsored run would still settle once CI is green.

中文说明

置信度:3/5——静态审查是真正干净的(未发现阻断问题,实现质量良好),但核心规模升级将其封顶在 3/5:需要维护者签核后才能批准,且受审 commit 的 CI 尚未落定。

退一步看:在读 diff 之前,我的独立判断是这个功能恰好需要三个数据源——只有 bridge 拥有的 live FIFO 状态、弥合 transcript 可见性间隙的终态结果、以及重启后有界的持久查询——而这正是本 PR 构建的,且每一部分都复用现有机制而非另起炉灶。三次迭代的演进方向正确:#8682 试图让结果存储在崩溃下持久(7.8k 行),#9080 收缩,本版本接受明确的 best-effort 契约并文档化其 miss 语义(prompt_not_found 不代表"从未存在")。这种对契约的诚实是范围收敛的关键设计决策,也是维护者应确认是否接受的点——部分消费方未来可能想要更强保证,而本 PR 明确拒绝提供。

让我谨慎的不是代码,而是它所处的位置:约 888 行生产代码横跨 core services、acp-bridge 与 acp-integration 高风险路径,来自 fork,新增默认开启、无开关的公开接口。每一点单独看都有正当理由——合在一起正是两级门禁要求人类做合并决定的原因。审查没有发现需要修复的问题;只是这个收尾决定不属于我。

两个不阻断的跟进事项:如果本 PR 替代 #9080#8682,请关闭它们;Stage 2 评论中的 @qwen-code /verify 一行说明了 CI 变绿后赞助运行仍可补齐的验证。

Qwen Code · qwen3.8-max

Reviewed at bbf24434c390c6ee1fd1856013937196ae0f60d1 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

⏸️ Deferring to @wenshao — clean static review (3/5, no blockers), but this one needs a human merge decision for three reasons:

  1. Core-scale escalation: ~888 production-logic lines across packages/core/src/services/**, packages/acp-bridge, and packages/cli (serve + acp-integration). feat-type so not size-blocked, but 500+ core production lines means maintainer awareness is mandatory before approval.
  2. High-risk paths + fork author: the diff touches packages/cli/src/acp-integration/ (revert-correlated in this repo's history) and comes from a fork; CI on the reviewed commit is still running.
  3. New always-on public surface: two GET routes and a session_turn_status capability with no feature flag — the deliberately best-effort contract (documented in the PR's design doc) is a product decision a maintainer should own.

Suggested lanes once CI settles: @qwen-code /verify (sponsored run) for A/B proof of the result-text boundary and restart-lookup semantics; also please close #9080 / #8682 if this supersedes them.

需要维护者做合并决定的原因:核心路径规模升级(约 888 行生产代码,feat 类型不阻断但必须知会)、高风险路径 + fork 来源且 CI 未落定、新增默认开启的公开接口(best-effort 契约属产品决策)。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head bbf2443, drove a fixed endpoint set against each, and diffed the JSON responses. Only fields that changed are shown.

capabilities

field PR base (before) this PR (after)
features[] "session_turn_status"

Qwen Code · serve A/B

@BenGuanRan BenGuanRan closed this Aug 14, 2026
@BenGuanRan
BenGuanRan deleted the feat/daemon-turn-status-polling-final branch August 14, 2026 08:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants