fix(ui): trim background task results and show newest first (#4094)#4125
fix(ui): trim background task results and show newest first (#4094)#4125wenshao wants to merge 3 commits into
Conversation
Two related improvements to the background task pill and dialog: 1. Trim outdated terminal task results. `BackgroundTaskRegistry` and `BackgroundShellRegistry` now cap retained terminal entries at 32 each (mirroring `MonitorRegistry`'s existing `MAX_RETAINED_TERMINAL_MONITORS` pattern). Running, paused, and cancelled-but-not-yet-notified entries are never evicted — pruning a not-yet-notified entry would break the SDK contract that every `register` pairs with exactly one terminal `task-notification`. 2. Show newest tasks at the top of the dialog. `useBackgroundTaskView` now sorts entries by `startTime` descending so the dialog opens with the cursor on the most recently launched task. `LiveAgentPanel` reverses internally back to ASC for its own visual layout (newest row sits closest to the composer).
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. |
wenshao
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: self-PR. — gpt-5.5 via Qwen Code /review
wenshao
left a comment
There was a problem hiding this comment.
No blocking issues found. Changes look solid — retention caps are well-designed with correct protection for running/paused/unnotified entries, sort flip is cleanly handled across all consumers, and tests have good coverage.
One minor observation: BackgroundShellRegistry.abortAll() now triggers O(N) prune+statusChange callbacks since each cancel() call invokes the new pruneTerminalEntries(). For typical session shutdown this is negligible, but consider batching into a single prune+callback pass in a follow-up.
— deepseek-v4-pro via Qwen Code /review
abortAll() previously delegated to cancel() per entry, so each running shell triggered its own pruneTerminalEntries() and statusChange wakeup. On shutdown / `/clear` with N running shells the only subscriber (useBackgroundTaskView) re-pulled getAll() N times for what is logically a single batch transition. Settle each entry inline via the new private settleAsCancelled() helper, then fire prune + statusChange exactly once after the loop. The split keeps the running-status guard at the public-API boundary so callers can't accidentally re-settle a terminal entry.
|
Good catch — addressed in 1c0b2e0.
Pinned the new behavior with two tests:
|
|
One thing I noticed after re-reading #4094: the current merged ordering is still purely Related small edge case: |
The earlier startTime DESC sort surfaced the newest LAUNCH but let an
older long-running / paused entry get pushed below a batch of newer
terminal entries — the user opening the dialog to check on the running
work would find it buried under stale completed rows.
Split the merge into two buckets:
- active (running + paused): sorted by startTime DESC so the most
recent launch sits at the very top of the dialog.
- terminal (completed / failed / cancelled): sorted by endTime DESC
so the most recently FINISHED entry leads the terminal section
(matches "what changed while I wasn't looking" intuition; a long
task that just settled outranks an old quick task that finished
hours ago).
Pin the new behavior with two tests covering active-above-terminal
and the endTime-vs-startTime distinction inside the terminal bucket.
|
Good catch — addressed in d3539b0. Re-read the issue and you're right that "new OR running" was only half-handled by the pure
Pinned with two new tests:
Also confirmed |
tmux 端到端验证在本地把 dist 重新构建后,在 tmux 里跑了真 qwen,对三个 commit 的行为做端到端校验。 单测PR 涉及的 4 个测试文件,131 个 case 全 pass:
真 qwen + tmux启动 qwen 后让 LLM 在一个 turn 内并行发起 5 个
等短任务跑完(剩 2 个 running、3 个 terminal),↓+Enter 打开 Background tasks 对话框,实测顺序: 光标默认停在最顶 active 行,符合 commit 排除旧逻辑这一结果同时排除两种旧排序:
detail 视图从对话框进 detail 视图也确认 另外两点
环境:Linux 6.12 / Node 20.19 / tmux 3.5a / |
Summary
Closes #4094.
Two related improvements to the background task pill and dialog:
Trim outdated terminal task results.
BackgroundTaskRegistryandBackgroundShellRegistrynow cap retained terminal entries at 32 each (mirroringMonitorRegistry's existingMAX_RETAINED_TERMINAL_MONITORSpattern). Running, paused, and cancelled-but-not-yet-notified entries are never evicted — pruning a not-yet-notified entry would break the SDK contract that everyregisterpairs with exactly one terminaltask-notification. Pruning runs on every status transition; oldest byendTime(thenstartTimeas tiebreaker) is evicted first.Show newest tasks at the top of the dialog.
useBackgroundTaskViewnow sorts entries bystartTimedescending so the dialog opens with the cursor on the most recently launched task (the user's natural ↓-into-dialog target after launching something).LiveAgentPanel(the borderless above-the-composer roster) keeps its existing visual order — newest row right above the prompt — by reversing internally back to ASC; the panel reads top-down toward the composer, where the newest entry deserves to land.Why these are paired
The two changes compound. Terminal-only retention without sort-flip would still bury freshly-launched tasks under stale rows for users with many short-lived completions. Sort-flip without retention would still let the dialog grow unbounded. Together they keep the dialog glanceable AND put the user-relevant entry in the cursor position.
What was NOT changed
MonitorRegistry's 128-cap is left as-is (monitors are auto-stopped, accumulate faster than user-initiated work).MAX_RETAINED_TERMINAL_DREAMS = 3)./tasksslash command still sorts ASC (tasksCommand.ts); it's a separate surface that prints a flat list and the issue is dialog-scoped — easy to flip in a follow-up if desired.unregisterForegroundand don't participate in the cap.Test plan
BackgroundShellRegistryretention cap (eviction by oldest endTime, running entries protected, fires from complete/fail/cancel)BackgroundTaskRegistryretention cap (running protected, paused protected, cancelled-but-not-notified protected, abandoned counts as terminal)useBackgroundTaskViewsorts entries newest-first across all four kinds (agent / shell / monitor / dream)LiveAgentPanelwindowing test updated to pass entries in the new newest-first contractBackgroundTasksDialog,BackgroundTasksPill,MonitorRegistry,tasksCommandtest suites all still pass (no regressions)