feat(skills): show live per-source progress while browsing (#43398)
do_browse waited on a frozen 'Fetching skills...' spinner while sources resolved, so a slow source looked like a hang. parallel_search_sources already exposes an on_source_done(sid, count) callback fired as each source completes — wire it into the status line so it ticks off sources live (official (12), + github (4), + clawhub (500)). The page is still rendered once, after the full set is merged and trust-sorted, so browse's official-first ordering and pagination contract are untouched.
This commit is contained in:
parent
eee1da45f0
commit
298bb93d39
@ -351,13 +351,29 @@ def do_browse(page: int = 1, page_size: int = 20, source: str = "all",
|
|||||||
"lobehub": 500, "browse-sh": 500,
|
"lobehub": 500, "browse-sh": 500,
|
||||||
}
|
}
|
||||||
|
|
||||||
with c.status("[bold]Fetching skills from registries..."):
|
with c.status("[bold]Fetching skills from registries...") as status:
|
||||||
|
# Live progress: tick off each source as it resolves so the wait is
|
||||||
|
# visible instead of a frozen spinner. parallel_search_sources invokes
|
||||||
|
# this callback from the collecting thread as each source completes;
|
||||||
|
# the page itself is still rendered once, after the correctly-merged
|
||||||
|
# and trust-sorted result set is final (browse's ordering contract is
|
||||||
|
# computed over the whole set, so we never render a half-sorted page).
|
||||||
|
_done: List[str] = []
|
||||||
|
|
||||||
|
def _on_source_done(sid: str, count: int) -> None:
|
||||||
|
_done.append(f"{sid} ({count})")
|
||||||
|
status.update(
|
||||||
|
"[bold]Fetching skills from registries...[/] "
|
||||||
|
f"[dim]done: {', '.join(_done)}[/]"
|
||||||
|
)
|
||||||
|
|
||||||
all_results, source_counts, timed_out = parallel_search_sources(
|
all_results, source_counts, timed_out = parallel_search_sources(
|
||||||
sources,
|
sources,
|
||||||
query="",
|
query="",
|
||||||
per_source_limits=_PER_SOURCE_LIMIT,
|
per_source_limits=_PER_SOURCE_LIMIT,
|
||||||
source_filter=source,
|
source_filter=source,
|
||||||
overall_timeout=30,
|
overall_timeout=30,
|
||||||
|
on_source_done=_on_source_done,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not all_results:
|
if not all_results:
|
||||||
|
|||||||
@ -653,6 +653,44 @@ def test_browse_skills_dedup_uses_identifier_not_name(monkeypatch):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_do_browse_reports_live_per_source_progress():
|
||||||
|
"""do_browse must pass an on_source_done callback so the status line ticks
|
||||||
|
off each source as it resolves, instead of showing a frozen spinner while
|
||||||
|
a slow source blocks. The page is still rendered once, after the full
|
||||||
|
result set is merged and trust-sorted."""
|
||||||
|
from hermes_cli.skills_hub import do_browse
|
||||||
|
from tools.skills_hub import SkillMeta
|
||||||
|
|
||||||
|
meta = SkillMeta(
|
||||||
|
name="demo", description="d", source="official",
|
||||||
|
identifier="official/demo", trust_level="builtin",
|
||||||
|
)
|
||||||
|
|
||||||
|
captured = {}
|
||||||
|
|
||||||
|
def fake_parallel(sources, query="", per_source_limits=None,
|
||||||
|
source_filter="all", overall_timeout=30,
|
||||||
|
on_source_done=None):
|
||||||
|
# Simulate two sources completing — the callback must be wired through.
|
||||||
|
assert on_source_done is not None, "do_browse must pass on_source_done"
|
||||||
|
on_source_done("official", 1)
|
||||||
|
on_source_done("clawhub", 0)
|
||||||
|
captured["called"] = True
|
||||||
|
return [meta], {"official": 1, "clawhub": 0}, []
|
||||||
|
|
||||||
|
sink = StringIO()
|
||||||
|
console = Console(file=sink, force_terminal=False, color_system=None, width=120)
|
||||||
|
|
||||||
|
with patch("tools.skills_hub.create_source_router", return_value=[]), \
|
||||||
|
patch("tools.skills_hub.GitHubAuth"), \
|
||||||
|
patch("tools.skills_hub.parallel_search_sources", side_effect=fake_parallel):
|
||||||
|
do_browse(page=1, page_size=20, console=console)
|
||||||
|
|
||||||
|
assert captured.get("called"), "parallel_search_sources was not invoked"
|
||||||
|
# The rendered page still shows the (single) merged result.
|
||||||
|
assert "demo" in sink.getvalue()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Regression: full identifier must be recoverable from `hermes skills search`
|
# Regression: full identifier must be recoverable from `hermes skills search`
|
||||||
# even when the slug is too long to fit the terminal width (issue #33674).
|
# even when the slug is too long to fit the terminal width (issue #33674).
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user