feat(skill-hub): browse + install + author Claude-format skills#577
feat(skill-hub): browse + install + author Claude-format skills#577martinma51 wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces the Skill Hub feature, allowing users to manage locally installed skills, author new skills via an in-UI markdown editor, and browse or install third-party skills from the ClawHub registry. Key feedback focuses on critical backend performance and resource management issues in skill_hub.py, specifically wrapping synchronous network I/O operations in asyncio.to_thread to prevent blocking the FastAPI event loop, and using with context managers for streaming HTTP requests to avoid connection leaks. On the frontend, it is recommended to wrap the loadSkill function in useCallback to maintain a stable reference and properly satisfy React's hook dependency rules.
Adds a Skill Hub to the xagent web UI — a browse/install/author
surface for Claude-format skills (the same SKILL.md + frontmatter
shape ``SkillManager`` already parses).
Three tabs:
Discover — browse ClawHub (openclaw.ai), the public registry of
Claude skills. One-click install with a featured rail
(editorially curated via skill_hub_featured.json), a
"Popular this week" rail, and a paginated browse-all grid
(Prev/Next, sortable, debounced search). Source picker is wired
for future HuggingFace / Anthropic catalogs.
My Skills — every skill the SkillManager sees, tagged builtin /
installed / external. Search + sort, remove via the project's
ConfirmDialog.
Detail / Create / Edit — view any installed skill's SKILL.md +
file tree; user-installed ones additionally get an in-browser
edit mode (textarea + live preview) and a "Create new" page
pre-populated with a starter template.
Built on top of the existing SkillManager — installs land in
``<storage_root>/skills/<slug>/`` (default ``~/.xagent/skills/``),
the writable root SkillManager already reads from.
Backend (``web/api/skill_hub.py``):
- Single router; lifecycle endpoints (list / get / create /
edit / delete) + ClawHub registry proxy (list / search /
detail / install) + featured / stats.
- Install policy is a blacklist (refuses upstream-flagged
malicious / quarantined / revoked). The gate lives
server-side, not in the UI.
- Editorial rail is config-driven via
``web/skill_hub_featured.json`` — backend re-reads on every
request, so the list updates without a restart.
- Startup prewarm primes Featured + stats caches in parallel
(fire-and-forget background task) so the first user request
to Discover is ~200ms instead of cold-cache ~5-10s.
Frontend (``app/skill-hub/``):
- page.tsx — Discover/My Skills tabs, source dropdown, sort
select, paginated grid.
- [name]/page.tsx — detail + in-place edit (split-pane editor).
- new/page.tsx — create from scratch with starter template.
- components/skill-hub/markdown-editor.tsx — shared split-pane
editor (textarea + live MarkdownRenderer preview).
- sessionStorage SWR cache (5 min TTL matching backend) makes
re-visits and refreshes feel instant.
Sidebar entry slots into the Resources group between Memory and
the "More" expandable, using a Library icon. i18n key
``nav.skillHub`` added to both en and zh base locales.
Notes on ClawHub's behavior (caught during dev, codified in
the implementation):
- sort=trending returns "top N at request limit" rather than
paginating, so the UI shows "Page 1 of 1" on that sort with
Next disabled. Default is sort=installsCurrent which
paginates over the full ~8k corpus.
- There's no total-count endpoint upstream; backend walks
cursor pages (capped at 15 × 100 items) to estimate total
pages, marks ``truncated=true`` and renders ``Page N of ≥M``
when the cap kicks in.
- Featured detail calls are fanned out with ``asyncio.gather``
so 6 sequential ~1s ClawHub round-trips become one ~1s
parallel batch.
…first call
The xagent startup-event integration tests (test_auto_migration_startup.py,
test_startup_file_storage_sync_wiring.py) track every asyncio.create_task
fired during ``@app.on_event('startup')`` and assert the tracked list is
empty. Our ``asyncio.create_task(skill_hub_prewarm())`` broke that
contract — 16 unrelated tests failed with 'Status code 204 must not have a
response body' (the assertion fires after task-creation tracking and looks
unrelated, but the upstream cause was the extra startup task).
Drop the prewarm function + its startup hook entirely. /featured and
/registry/stats endpoints already populate their own caches on first
call; the first user pays ~2.5s instead of getting an already-warm
cache. Frontend SWR + sessionStorage absorb subsequent hits across
pages and refreshes.
85d3bcc to
27bf0aa
Compare
…ass for DELETE Match the convention used by other 204 endpoints in the codebase (custom_api.py, mcp.py). The previous ``status_code=204`` raw int + ``-> None`` return annotation pattern was triggering FastAPI's 'Status code 204 must not have a response body' assertion in CI but not locally — same fastapi version, same code, different outcome. Going with the most defensive 204-declaration shape resolves the ambiguity.
- Wrap synchronous `_clawhub_json` / `_HTTP.get` (download) calls in
`asyncio.to_thread` so registry list/search/detail and install no
longer block the event loop while ClawHub responds.
- Use `with _HTTP.get(...) as r:` context managers so streaming
connections are returned to the pool even when the body read
raises or hits the size cap.
- Replace the leftover `__import__("json").loads(...)` with the
already-imported `json` module.
- Wrap `loadSkill` in `useCallback` on the skill detail page so the
`useEffect` deps array no longer needs the eslint-disable escape
hatch.
|
Address gemini-code-assist review feedback (commit 7d9fe1d):
Verified locally before push:
|
Summary
Adds a Skill Hub to xagent's web UI — a browse/install/author surface for Claude-format skills. Built on top of the existing
SkillManager; nothing about the core skill loader changes.Three tabs:
web/skill_hub_featured.json), "Popular this week" rail, paginated browse-all with sort + debounced search.Installs land in
<storage_root>/skills/<slug>/(default~/.xagent/skills/), the writable root SkillManager already reads from.Files
Architecture notes
malicious/quarantined/revoked. The gate lives server-side, not in the UI.asyncio.create_taskon the existing@app.on_event("startup"). Fire-and-forget; failures are logged but don't block uvicorn.asyncio.gatherso 6 sequential ~1s ClawHub round-trips become one ~1s parallel batch.ClawHub gotchas (codified in the implementation)
sort=trendingreturns "top N at request limit" rather than paginating — UI shows "Page 1 of 1" on that sort with Next disabled. Default isinstallsCurrentwhich paginates over the full ~8k corpus.truncated=trueand renders "Page N of ≥M" when the cap kicks in.Test plan
/skill-hub.Newest/Most installedresets to page 1 and updates the total-page count.userskill.Out of scope (deliberate)