Skip to content

Commit e32568c

Browse files
authored
fix(shell): show active agent task count in prompt status bar (#2041)
1 parent 1e45df0 commit e32568c

6 files changed

Lines changed: 80 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Only write entries that are worth mentioning to users.
1111

1212
## Unreleased
1313

14+
- Shell: Show active background agent task count in the prompt status bar — the existing `⚙ bash: N` badge only counted background Shell tasks and filtered out background Agent subagents, so when many subagents were running the prompt looked idle and users could not tell work was in progress; the toolbar now renders `⚙ bash: N` and `⚙ agent: N` as two independent badges (each hidden when its count is 0) and drops the agent badge first when the terminal is too narrow to fit both
15+
1416
## 1.39.0 (2026-04-24)
1517

1618
- Skill: Fix project-scope skills being ignored and user-scope skills silently winning name conflicts — the system prompt now groups discovered skills under `### Project` / `### User` / `### Extra` / `### Built-in` headings so the model can tell where each skill came from, and when the same name exists in multiple scopes the more specific scope wins (Project > User > Extra > Built-in) so a project's own `.kimi/skills/foo` or `.claude/skills/foo` correctly overrides a user-level or bundled `foo` instead of the other way around

docs/en/release-notes/changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ This page documents the changes in each Kimi Code CLI release.
44

55
## Unreleased
66

7+
- Shell: Show active background agent task count in the prompt status bar — the existing `⚙ bash: N` badge only counted background Shell tasks and filtered out background Agent subagents, so when many subagents were running the prompt looked idle and users could not tell work was in progress; the toolbar now renders `⚙ bash: N` and `⚙ agent: N` as two independent badges (each hidden when its count is 0) and drops the agent badge first when the terminal is too narrow to fit both
8+
79
## 1.39.0 (2026-04-24)
810

911
- Skill: Fix project-scope skills being ignored and user-scope skills silently winning name conflicts — the system prompt now groups discovered skills under `### Project` / `### User` / `### Extra` / `### Built-in` headings so the model can tell where each skill came from, and when the same name exists in multiple scopes the more specific scope wins (Project > User > Extra > Built-in) so a project's own `.kimi/skills/foo` or `.claude/skills/foo` correctly overrides a user-level or bundled `foo` instead of the other way around

docs/zh/release-notes/changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
## 未发布
66

7+
- Shell:在提示框状态栏显示当前正在运行的后台 Agent 任务数——原有的 `⚙ bash: N` 徽章只统计后台 Shell 任务,把后台 Agent 子代理过滤掉了,所以多个子代理同时在跑时提示框看起来像空闲,用户无法判断工作是否还在进行;现在状态栏会渲染 `⚙ bash: N``⚙ agent: N` 两个相互独立的徽章(任一计数为 0 时自动隐藏),终端太窄无法同时容纳两者时优先丢弃 agent 徽章
8+
79
## 1.39.0 (2026-04-24)
810

911
- Skill:修复项目级 Skill 被忽略、用户级 Skill 在同名冲突时静默获胜的问题——系统提示现在会把发现到的 Skill 按 `### Project` / `### User` / `### Extra` / `### Built-in` 四个分组呈现,让模型能分辨出每个 Skill 来自哪一层;当同一 Skill 名称同时存在于多个作用域时,越具体的作用域优先(Project > User > Extra > Built-in),项目自身的 `.kimi/skills/foo``.claude/skills/foo` 现在能正确覆盖用户级或内置的同名 `foo`,而不是被它们覆盖

src/kimi_cli/ui/shell/__init__.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from kimi_cli.ui.shell.echo import render_user_echo_text
3434
from kimi_cli.ui.shell.mcp_status import render_mcp_prompt
3535
from kimi_cli.ui.shell.prompt import (
36+
BgTaskCounts,
3637
CustomPromptSession,
3738
CwdLostError,
3839
PromptMode,
@@ -416,26 +417,28 @@ def _mcp_status_loading() -> bool:
416417
@dataclass
417418
class _BgCountCache:
418419
time: float = 0.0
419-
count: int = 0
420+
counts: BgTaskCounts = BgTaskCounts()
420421

421422
_bg_cache = _BgCountCache()
422423

423-
def _bg_task_count() -> int:
424+
def _bg_task_counts() -> BgTaskCounts:
424425
if not isinstance(self.soul, KimiSoul):
425-
return 0
426+
return BgTaskCounts()
426427
now = time.monotonic()
427428
if now - _bg_cache.time < 1.0:
428-
return _bg_cache.count
429+
return _bg_cache.counts
429430
views = list_task_views(self.soul.runtime.background_tasks, active_only=True)
430-
_bg_cache.count = sum(1 for v in views if v.spec.kind == "bash")
431+
bash_n = sum(1 for v in views if v.spec.kind == "bash")
432+
agent_n = sum(1 for v in views if v.spec.kind == "agent")
433+
_bg_cache.counts = BgTaskCounts(bash=bash_n, agent=agent_n)
431434
_bg_cache.time = now
432-
return _bg_cache.count
435+
return _bg_cache.counts
433436

434437
with CustomPromptSession(
435438
status_provider=lambda: self.soul.status,
436439
status_block_provider=_mcp_status_block,
437440
fast_refresh_provider=_mcp_status_loading,
438-
background_task_count_provider=_bg_task_count,
441+
background_task_count_provider=_bg_task_counts,
439442
model_capabilities=self.soul.model_capabilities or set(),
440443
model_name=model_display_name(
441444
self.soul.model_name,

src/kimi_cli/ui/shell/prompt.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,12 @@ def should_handle_running_prompt_key(self, key: str) -> bool: ...
10941094
def handle_running_prompt_key(self, key: str, event: KeyPressEvent) -> None: ...
10951095

10961096

1097+
@dataclass(frozen=True, slots=True)
1098+
class BgTaskCounts:
1099+
bash: int = 0
1100+
agent: int = 0
1101+
1102+
10971103
@runtime_checkable
10981104
class AgentStatusProvider(Protocol):
10991105
"""Optional protocol for delegates that render always-visible agent status.
@@ -1170,7 +1176,7 @@ def __init__(
11701176
status_provider: Callable[[], StatusSnapshot],
11711177
status_block_provider: Callable[[int], AnyFormattedText | None] | None = None,
11721178
fast_refresh_provider: Callable[[], bool] | None = None,
1173-
background_task_count_provider: Callable[[], int] | None = None,
1179+
background_task_count_provider: Callable[[], BgTaskCounts] | None = None,
11741180
model_capabilities: set[ModelCapability],
11751181
model_name: str | None,
11761182
thinking: bool,
@@ -2149,16 +2155,23 @@ def _render_bottom_toolbar(self) -> FormattedText:
21492155
fragments.extend([(tc.cwd, cwd_text), ("", " ")])
21502156
remaining -= cwd_w + 2
21512157

2152-
# Active background bash task count
2153-
bg_count = (
2154-
self._background_task_count_provider() if self._background_task_count_provider else 0
2158+
# Active background task counts (bash + agent, each rendered as its own
2159+
# badge). Order matters: bash renders first; if there isn't room for the
2160+
# agent badge too, drop agent and keep bash.
2161+
bg_counts = (
2162+
self._background_task_count_provider()
2163+
if self._background_task_count_provider
2164+
else BgTaskCounts()
21552165
)
2156-
if bg_count > 0:
2157-
bg_text = f"⚙ bash: {bg_count}"
2166+
for kind_label, kind_count in (("bash", bg_counts.bash), ("agent", bg_counts.agent)):
2167+
if kind_count <= 0:
2168+
continue
2169+
bg_text = f"⚙ {kind_label}: {kind_count}"
21582170
bg_width = _display_width(bg_text)
2159-
if remaining >= bg_width + 2:
2160-
fragments.extend([(tc.bg_tasks, bg_text), ("", " ")])
2161-
remaining -= bg_width + 2
2171+
if remaining < bg_width + 2:
2172+
break
2173+
fragments.extend([(tc.bg_tasks, bg_text), ("", " ")])
2174+
remaining -= bg_width + 2
21622175

21632176
# Tips fill remaining space on line 1
21642177
tip_text = self._get_two_rotating_tips()

tests/ui_and_conv/test_prompt_tips.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from kimi_cli.ui.shell.prompt import (
1515
_GIT_STATUS_TTL,
1616
PROMPT_SYMBOL,
17+
BgTaskCounts,
1718
CustomPromptSession,
1819
PromptMode,
1920
UserInput,
@@ -332,7 +333,7 @@ def test_bottom_toolbar_narrow_terminal_with_full_decoration(width: int, monkeyp
332333
model_name="kimi-latest",
333334
tips=["ctrl-x: toggle mode"],
334335
)
335-
prompt_session._background_task_count_provider = lambda: 2
336+
prompt_session._background_task_count_provider = lambda: BgTaskCounts(bash=2, agent=0)
336337

337338
lines = _render_toolbar_lines(
338339
prompt_session,
@@ -352,6 +353,46 @@ def test_bottom_toolbar_narrow_terminal_with_full_decoration(width: int, monkeyp
352353
)
353354

354355

356+
def test_bottom_toolbar_shows_bash_and_agent_badges_together(monkeypatch: Any) -> None:
357+
prompt_session = _make_toolbar_session(tips=[])
358+
prompt_session._background_task_count_provider = lambda: BgTaskCounts(bash=3, agent=1)
359+
360+
lines = _render_toolbar_lines(prompt_session, 120, monkeypatch)
361+
362+
assert "⚙ bash: 3" in lines[1], f"bash badge missing: {lines[1]!r}"
363+
assert "⚙ agent: 1" in lines[1], f"agent badge missing: {lines[1]!r}"
364+
assert lines[1].index("⚙ bash: 3") < lines[1].index("⚙ agent: 1"), (
365+
f"bash badge must come before agent badge: {lines[1]!r}"
366+
)
367+
368+
369+
def test_bottom_toolbar_shows_agent_badge_alone_when_no_bash(monkeypatch: Any) -> None:
370+
prompt_session = _make_toolbar_session(tips=[])
371+
prompt_session._background_task_count_provider = lambda: BgTaskCounts(bash=0, agent=2)
372+
373+
lines = _render_toolbar_lines(prompt_session, 120, monkeypatch)
374+
375+
assert "⚙ bash" not in lines[1], f"bash badge must not appear when count is 0: {lines[1]!r}"
376+
assert "⚙ agent: 2" in lines[1], f"agent badge missing: {lines[1]!r}"
377+
378+
379+
def test_bottom_toolbar_drops_agent_badge_before_bash_when_narrow(monkeypatch: Any) -> None:
380+
# With only ~width budget for one badge after CWD/mode, keeping bash and
381+
# dropping agent is the documented priority.
382+
prompt_session = _make_toolbar_session(tips=[])
383+
prompt_session._background_task_count_provider = lambda: BgTaskCounts(bash=5, agent=5)
384+
385+
lines = _render_toolbar_lines(prompt_session, 40, monkeypatch)
386+
387+
# Must never overflow and the bash badge is preferred over the agent badge.
388+
assert _display_width(lines[1]) <= 40
389+
if "⚙ agent" in lines[1]:
390+
# Only acceptable if bash also fit — otherwise priority is violated.
391+
assert "⚙ bash" in lines[1], (
392+
f"agent badge appeared without bash badge at narrow width: {lines[1]!r}"
393+
)
394+
395+
355396
def test_mode_shows_full_with_model_name_on_wide_terminal(monkeypatch: Any) -> None:
356397
"""On a wide terminal the full mode string (with model name and thinking dot) is shown."""
357398
session = _make_toolbar_session(model_name="fast-model")

0 commit comments

Comments
 (0)