fix(cli): hide sticky task panel when agent is idle - #7062
Conversation
Add agents.maxParallelAgentsByModel, a per-model concurrency cap for background sub-agents keyed by concrete model ID. It complements the existing global agents.maxParallelAgents cap: a per-model cap can only reduce, never exceed, the global limit, and models without an entry fall back to the global cap. - Track the resolved concrete model ID on each background AgentTask (resolved from the sub-agent's model selector at launch via resolveModelId, falling back to the parent model when inherited). - Make BackgroundTaskRegistry slot accounting model-aware: reservations and the wait-queue drain now count claimed slots per model in addition to the global count. - Wire the setting through settingsSchema -> CLI config -> core Config -> BackgroundTaskRegistry; malformed per-model values are ignored. - Honor the per-model cap on the same-session revive path.
The sticky "Current tasks" panel remained visible with stale in-progress indicators after the agent finished all work and the conversation ended, confusing users into thinking something was still running. Previously shouldShowStickyTodos only excluded WaitingForConfirmation; it showed for both Responding and Idle. Change the condition to require streamingState === Responding so the panel only appears during active agent work. Updated tests in DefaultAppLayout and ScreenReaderAppLayout: - baseUIState now uses StreamingState.Responding (panel visible) - Added test: panel hidden when streamingState is Idle
|
Thanks for the PR! Template: headings don't match the repo template ( Problem: the sticky panel bug is real — issue #7061 documents it clearly with a reproduction. The panel stays visible with stale ◐ indicators after the agent finishes. ✓ Direction: the sticky panel fix is well-aligned — hiding the panel unless actively streaming is the right call. CHANGELOG has similar UI state fixes in past releases. However, this PR carries a second, substantial feature that is entirely undocumented: per-model background agent concurrency caps ( Size: ~261 production logic lines in core paths, ~203 test lines, ~69 CLI production lines. The sticky panel fix itself is ~6 production lines across 3 UI files. The remaining ~327 production lines are the undocumented per-model concurrency feature. Approach: two independent changes are bundled into one PR. The sticky panel fix (3 files, ~6 lines) is clean and minimal. The per-model concurrency feature (~327 production lines, 11 files including core infrastructure) is a separate feature that deserves its own PR with its own description, motivation, and review. Combining them makes it hard to review either change properly and means the per-model feature ships without any documented rationale. Strong recommendation: split into two PRs. One for the sticky panel fix (trivial to review and merge), one for the per-model concurrency cap (needs proper review of the registry changes, wait queue logic, and model resolution). Moving on to code review. 🔍 中文说明感谢贡献! 模板:标题格式与仓库模板不一致(用了 问题:sticky panel bug 是真实存在的——issue #7061 有清晰的复现。agent 完成后面板仍然显示过期的 ◐ 指示器。✓ 方向:sticky panel 的修复方向正确——只在 actively streaming 时显示面板。 但此 PR 夹带了一个完全未记录的第二功能: 按模型设置后台 agent 并发上限( 规模:核心路径约 261 行生产代码,约 203 行测试代码,CLI 约 69 行生产代码。Sticky panel 修复本身只有 3 个 UI 文件约 6 行。剩余约 327 行生产代码都是未记录的按模型并发功能。 方案:两个独立改动被打包在一个 PR 里。Sticky panel 修复(3 文件约 6 行)简洁最小化。按模型并发功能(约 327 行生产代码,11 个文件含核心基础设施)是一个独立功能,应有自己的 PR、描述和审查。合并在一起让两者都难以充分审查,且按模型功能没有任何文档化的设计理由就发布了。 强烈建议:拆成两个 PR。 一个用于 sticky panel 修复(容易审查和合并),一个用于按模型并发上限(需要仔细审查 registry 变更、等待队列逻辑和模型解析)。 进入代码审查 🔍 — Qwen Code · qwen3.7-max Reviewed at |
Code ReviewSticky panel fix (3 UI files, ~6 lines): Clean, minimal change. The condition flip from Per-model background agent concurrency (~327 production lines, 11 files): The implementation is thorough and well-structured. The
No correctness bugs, security issues, or AGENTS.md violations found in the implementation itself. Scope mismatch remains the primary concern (flagged in Stage 1): the per-model concurrency feature is entirely undocumented in the PR title and body. The title says "hide sticky task panel" but ~98% of the production lines implement a separate, undocumented feature touching core infrastructure ( Unit TestsReal-Scenario Testing (tmux)The sticky panel is a TUI-only feature that renders during interactive sessions — headless Smoke test confirms the dev build starts and responds correctly with the PR code: No crashes, no startup errors. The per-model concurrency setting path is exercised by the 110 background-tasks unit tests including edge cases (malformed entries, queue draining, model-specific caps). 中文说明代码审查Sticky panel 修复(3 个 UI 文件,约 6 行): 干净、最小化的改动。条件从 按模型后台 agent 并发(约 327 行生产代码,11 个文件): 实现彻底且结构良好。 范围不匹配仍然是主要问题(在 Stage 1 中标记):按模型并发功能在 PR 标题和正文中完全没有记录。标题说的是"隐藏 sticky task panel",但约 98% 的生产代码行实现了一个触及核心基础设施的独立功能。 单元测试全部通过:15 个布局测试、110 个后台任务测试、275 个 CLI 配置测试、396 个核心配置测试。TypeScript 类型检查通过。 真实场景测试(tmux)Sticky panel 是仅在交互式会话中渲染的 TUI 功能——无头 冒烟测试确认 dev build 正常启动和响应,无崩溃。按模型并发路径由 110 个后台任务单元测试覆盖。 — Qwen Code · qwen3.7-max Reviewed at |
|
Confidence: 3/5 — implementation is clean across every stage, but the scope mismatch needs a maintainer's call. The sticky panel fix is exactly right — 3 files, 6 lines, does what the issue asks, tests confirm it. If this PR only contained that change, it would be a 5/5 approve. The per-model background agent concurrency feature is well-built — the registry changes are internally consistent, the wait queue drain logic correctly handles per-model blocking without stalling unrelated models, and the 110 unit tests cover edge cases thoroughly. But it's a ~327-line feature touching core infrastructure ( Shipping a substantial core infrastructure change without any documented rationale makes it harder to maintain — six months from now, someone reading the PR description will see "hide sticky task panel" and have no idea why the Recommendation: split into two PRs. The sticky panel fix is trivial to review and can merge immediately. The per-model concurrency feature deserves its own PR with its own description and motivation so it can be reviewed (and potentially reverted) independently. Deferring to maintainer for the scope decision. 中文说明置信度:3/5 — 实现在每个阶段都干净,但范围不匹配需要维护者决定。 Sticky panel 修复完全正确——3 个文件、6 行代码、做了 issue 要求的事、测试确认。如果这个 PR 只包含这个改动,就是 5/5 通过。 按模型后台 agent 并发功能实现良好——registry 改动内部一致,等待队列 drain 逻辑正确处理了按模型阻塞而不影响其他模型,110 个单元测试彻底覆盖了边缘情况。但这是一个约 327 行的功能,触及核心基础设施( 在没有记录设计理由的情况下发布一个重大核心基础设施改动,会使维护更困难——六个月后,读 PR 描述的人只会看到"隐藏 sticky task panel",完全不知道为什么 建议: 拆成两个 PR。Sticky panel 修复容易审查,可以立即合并。按模型并发功能应有自己的 PR、描述和动机,以便独立审查(和潜在回滚)。 转交维护者决定范围问题。 — Qwen Code · qwen3.7-max Reviewed at |
|
⏸️ Deferring to @wenshao — this PR bundles two independent changes: a clean sticky panel fix (trivial to approve) and a substantial per-model background agent concurrency feature (~327 production lines in core infrastructure) that is entirely undocumented in the PR title/body. The implementation is solid, but shipping a core infrastructure restructuring without any documented rationale is a maintainability concern. Needs a maintainer call on whether to accept as-is or request a split into two PRs. |
E2E Test Report — tmux real-device verificationEnvironment: qwen-code v0.19.11 (patched with this PR's fix), Linux, tmux 200×50, model qwen3.7-max, YOLO mode. Test procedure:
WITHOUT the fix (original v0.19.11 code)After the agent finishes, the sticky "Current tasks" panel remains visible with stale in-progress indicators: The panel shows WITH the fix appliedAfter the agent finishes with the same prompt, the sticky "Current tasks" panel disappears completely: No "Current tasks" panel. The agent is idle and the stale in-progress indicators are gone. ConclusionThe fix correctly hides the sticky task panel when |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. 1 Suggestion-level finding(s) could not be anchored to the diff; see the terminal output.
— qwen3.7-max via Qwen Code /review
| subagentModelId = resolveModelId( | ||
| subagentConfig.model, | ||
| buildModelIdContext(this.config), | ||
| )?.modelId; |
There was a problem hiding this comment.
[Suggestion] The agent tool's model resolution pass-through to tryReserveBackgroundSlot/waitForBackgroundSlot is not tested at the integration level — existing mocks in agent.test.ts return { id: Symbol('background-slot') } unconditionally and never assert on the model argument. — Failure scenario: a future edit that drops the subagentModelId argument from tryReserveBackgroundSlot(subagentModelId) would silently disable per-model cap enforcement on the primary background-agent launch path, with no test detecting the regression.
Consider adding an assertion like expect(mockRegistry.tryReserveBackgroundSlot).toHaveBeenCalledWith(expect.anything()) or verifying the register call includes model.
— qwen3.7-max via Qwen Code /review
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. 1 Suggestion-level finding(s) could not be anchored to the diff; see the terminal output.
— qwen3.7-max via Qwen Code /review
| requiresRestart: true, | ||
| default: undefined as Record<string, number> | undefined, | ||
| description: | ||
| 'Per-model maximum number of background sub-agents that can run concurrently, keyed by model ID (e.g. { "qwen3-max": 2 }). Useful when a model has a lower concurrency capacity. Takes precedence over the global maxParallelAgents for the matched model; models not listed here fall back to the global limit.', |
There was a problem hiding this comment.
[Suggestion] The description says "Takes precedence over the global maxParallelAgents" but the implementation enforces min(global, perModel) — the global cap always wins. The test "enforces the global cap even when the per-model cap has room" explicitly proves this: a per-model cap of 5 with a global cap of 1 results in an effective cap of 1. The same inaccurate phrasing ("Overrides the global maxParallelAgents") also appears in the JSDoc at packages/core/src/config/config.ts:837.
Failure scenario: A user sets maxParallelAgentsByModel: { "weak-model": 5 } with global maxParallelAgents: 2, expecting weak-model to run up to 5 agents. The actual cap is 2 — the description misleads.
| 'Per-model maximum number of background sub-agents that can run concurrently, keyed by model ID (e.g. { "qwen3-max": 2 }). Useful when a model has a lower concurrency capacity. Takes precedence over the global maxParallelAgents for the matched model; models not listed here fall back to the global limit.', | |
| 'Per-model maximum number of background sub-agents that can run concurrently, keyed by model ID (e.g. { "qwen3-max": 2 }). Useful when a model has a lower concurrency capacity. The effective cap for a listed model is the lower of this value and the global maxParallelAgents; models not listed here use the global limit.', |
— qwen3.7-max via Qwen Code /review
|
🤖 Could not address the latest feedback automatically (round 1/5). A human should take over this PR. What I found before stopping: Address-Review Summary — PR #7062Feedback Triage1. Stale settings schema (Required — CI failure root cause) ✅ FixedFeedback: CI "Test (ubuntu-latest Node 22.x)" failed. The "Check settings schema Diagnosis: Reproduced by running Fix: Regenerated Commit: 2. PR bundles two independent changes (Optional — maintainer decision) ⏸️ DeferredFeedback: The PR combines (a) a trivial sticky-panel fix ( Decision: This is a product/scope decision that only a maintainer can make. Run log: https://github.com/QwenLM/qwen-code/actions/runs/29555723913 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Not reviewed: chunk 1 — no prompt was built for it (agent-prompt --chunk 1 never ran). Not reviewed: chunk 2 — no prompt was built for it (agent-prompt --chunk 2 never ran). Not reviewed: chunk 3 — no prompt was built for it (agent-prompt --chunk 3 never ran). Not reviewed: Test coverage matrix (whole-diff) — no prompt was built for it (agent-prompt --role test-matrix never ran).
— qwen3.7-max via Qwen Code /review
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Unresolved, please confirm: [Critical] Triage comment 4998196940 recommends splitting PR (sticky panel fix vs per-model concurrency feature) — scope decision for maintainer, not a code defect
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: coverage — no plan was given, so this run cannot show that any of the diff was read.
[Critical] Background fork agents bypass per-model concurrency caps: subagentModelId is resolved only inside the !isFork guard (agent.ts:2356), so fork agents running in the background are registered with model: undefined. assertCanStartBackgroundAgent(undefined) skips the per-model cap check, allowing unlimited forks on a capped model.
— qwen3.7-max via Qwen Code /review
| // stranding the entry as paused. | ||
| try { | ||
| registry.assertCanStartBackgroundAgent(); | ||
| registry.assertCanStartBackgroundAgent(entry.model); |
There was a problem hiding this comment.
[Suggestion] The resume recovery registration at line ~425 never sets model on the AgentTaskRegistration, so this assertCanStartBackgroundAgent(entry.model) call always receives undefined and the per-model cap check is skipped entirely on the revive path. — Failure scenario: user configures maxParallelAgentsByModel: { 'gemini-2.5-pro': 1 }, process crashes, on restart all recovered agents on that model are revived without the per-model cap being enforced.
Consider persisting the resolved model ID in AgentMeta (add model?: string) and reading it back in the recovery registration.
— qwen3.7-max via Qwen Code /review
…ume recovery (#7062) Fork agents running in the background bypassed per-model caps because subagentModelId was resolved only inside the !isFork guard, leaving forks registered with model: undefined. Remove that guard so forks also get model resolution (inherit from parent) and slot reservation. Persist the resolved model ID in AgentMeta so process-restart recovery can enforce per-model caps on the revive path. Regenerate settings schema to match the maxParallelAgentsByModel addition.
|
🤖 Addressed the latest review feedback (round 2/5). What changed, and what I pushed back on: Address Review Summary — PR #7062Feedback Points1. [Critical] Fork agents bypass per-model concurrency capsSource: @qwen-code-ci-bot inline review Problem: Fix: Removed the
Files changed: 2. [Suggestion] Resume recovery never sets model on AgentTaskRegistrationSource: @qwen-code-ci-bot inline comment on Problem: The recovery registration in Fix:
Files changed: 3. [Critical] Triage comment recommends splitting PRSource: @qwen-code-ci-bot triage comment The triage comment recommends splitting the sticky panel fix from the per-model concurrency feature. This is a scope/workflow decision, not a code defect. The two changes share the PR branch and are independently testable; splitting would be a maintainer call. 4. Not reviewed: build-and-test — Integration Tests skippedSource: @qwen-code-ci-bot 5. Not reviewed: coverage — no plan givenSource: @qwen-code-ci-bot Settings Schema RegenerationThe settings schema ( Conflict NotesNo merge conflicts — Verification ResultsAll checks passed:
Base-conflict check: no conflict with main. Re-review when you have a moment. After round 5 this bot stops and leaves the PR for a human. |
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. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
— qwen3.7-max via Qwen Code /review
| if (shouldRunInBackground) { | ||
| // Resolve the concrete model the sub-agent (or fork) will run with so the | ||
| // registry can apply a per-model cap. `subagentConfig.model` is a |
There was a problem hiding this comment.
[Suggestion] The !isFork guard was removed from the slot reservation condition, silently subjecting fork agents to background slot reservation and per-model concurrency caps. No test covers the fork + run_in_background: true combination.
Failure scenario: a caller passes subagent_type: "fork" with run_in_background: true. Previously, the fork started immediately without consuming a background slot. Now, if the global or per-model cap is reached, the fork queues behind other agents — an undocumented behavioral change affecting fork latency under cap pressure.
Consider adding a test in agent.test.ts that verifies a fork with run_in_background: true correctly reserves a slot (with the resolved parent model) and is subject to per-model caps.
— qwen3.7-max via Qwen Code /review
…model caps (#7062) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
🤖 Addressed the latest review feedback (round 3/5). What changed, and what I pushed back on: Address Review Summary — PR #7062Feedback ClassificationInline comment:
|
| Command | Result |
|---|---|
npm run build |
✅ Pass |
npm run typecheck |
✅ Pass |
npm run lint |
✅ Pass |
npx vitest run src/tools/agent/agent.test.ts (153 tests) |
✅ Pass |
npx vitest run src/agents/background-tasks.test.ts (110 tests) |
✅ Pass |
Base-conflict check: no conflict with main.
Re-review when you have a moment. After round 5 this bot stops and leaves the PR for a human.
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: No action needed on PR #7062Feedback triageInline comment:
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
— qwen3.7-max via Qwen Code /review
Maintainer local verification — real build + E2E ✅Built this branch locally and drove both behaviors end-to-end against the real bundle / real compiled Verified at Build & gates
① Sticky task panel (issue #7061) — real interactive TUI A/BDrove the real built CLI in a PTY against a fake model that creates a todo list (task 2
The fix is correctly scoped: it hides the panel only when the agent is idle, and still shows it during active streaming (the ② Per-model background-agent concurrency cap — real E2E on built
|
| 时刻 | base(改前) | PR #7062(改后) |
|---|---|---|
待办后回合中(Idle,快照后 <2 项) |
隐藏 | 隐藏 |
agent 结束 → Idle |
可见 ← #7061 的 bug | 隐藏 ← 已修复 |
活跃流式中(Responding) |
可见 | 可见 |
修复范围恰当:只在 idle 时隐藏面板,活跃流式时仍显示(Responding 帧证明不是一刀切移除)。完全复现了 #7061 并确认修复有效。
② 按模型后台 agent 并发上限 —— 在构建产物 dist 上的真实 E2E
驱动实际编译的 BackgroundTaskRegistry 与 Config,走完整生产公有 API(reserve / register / wait-queue / release)。19/19 检查通过,包含两个微妙点:
- settings →
Config→ registry 接线确实激活了 cap(不仅是孤立的 registry)。 - 等待队列索引扫描(
drainWaitQueue重写):队首被按模型 cap 阻塞的 waiter 不会卡住其后不同模型的 waiter——strong-model waiter 立即被服务,weak-model waiter 保持排队,待 weak slot 释放后再 drain。 - 格式错误条目归一化:
{"":3,"neg":-1,"zero":0,"float":1.5,"ok":2}退化为仅{ok:2};被丢弃的条目回退到全局 cap。 - 两个 round-2 review 修复均成立(专项测试通过):fork agent 继承父模型并计入其 cap;resume 从 meta sidecar 恢复
model。
A/B 对照实验 —— 同一驱动,设置 { "weak-model": 1 }:
- base:第 2 个 weak-model agent 被允许 → 并发 2 个(按模型 cap 被静默忽略)。
- PR:第 2 个 weak-model agent 被阻止 → 恰好 1 个运行。
实现中未发现正确性、安全或等待队列饥饿问题。
建议
两个改动都正确且测试充分——从代码质量角度可合并。剩下的决定纯属范围/历史整洁度,由你定夺:
- 首选:拆成两个 PR —— 6 行的 sticky panel 修复(简单,关闭 Sticky task panel remains visible after all tasks finish and conversation ends #7061)可立即合并;约 327 行的按模型并发功能单独开 PR + 描述,以便独立审查和回滚。或
- 保持现状合并,但先更新 PR 标题/正文以记录
agents.maxParallelAgentsByModel功能及其动机,避免git log把BackgroundTaskRegistry重构记为"hide sticky task panel"。
两条路在技术上都可行;决定权交给你。
Local real-build verification by the maintainer. Both features exercised end-to-end against the compiled bundle/dist; screenshots are real captured frames.
|
|
🤖 Addressed the latest review feedback (round 4/5). What changed, and what I pushed back on: Address review summary — PR #7062Feedback received@wenshao (maintainer) — 2 comments
Classification
Changes madeSingle commit: Reverted 13 files to their
All reverted files are byte-identical to What remains (sticky panel fix — #7061)5 files changed, all in
Verification
Conflict notesNo conflicts. Base-conflict check: no conflict with main. Re-review when you have a moment. After round 5 this bot stops and leaves the PR for a human. |
* fix(cli): hide sticky todo panel when a new turn starts PR QwenLM#7062 hid the sticky panel when streamingState is Idle, but the panel resurfaces with stale todos from a previous turn as soon as the user sends a new message (state → Responding). This is confusing — the user sees in-progress indicators for work that already finished in an earlier turn. Add a turn-boundary check in getStickyTodos: if a user message exists after the todo snapshot in history, the snapshot belongs to a previous turn and the sticky panel returns null. Fixes QwenLM#7061 * test(cli): pin sticky-todo turn boundary for local slash commands (QwenLM#7061) --------- Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>


Summary
Fixes #7061.
The sticky "Current tasks" panel remained visible with stale in-progress indicators (◐) after the agent finished all work and the conversation ended. Users saw the panel and thought something was still running.
Root cause
shouldShowStickyTodosonly excludedWaitingForConfirmation— it showed for bothRespondingandIdle. When the agent finished (state →Idle), the panel stayed visible even though no work was happening.Fix
Changed the condition in three places (AppContainer, DefaultAppLayout, ScreenReaderAppLayout) from:
to:
The panel now only appears during active agent streaming. When the agent finishes and becomes idle, the panel automatically disappears.
Behavior
Tests
baseUIStatein DefaultAppLayout and ScreenReaderAppLayout tests to useStreamingState.Responding