Skip to content

feat(skill-hub): browse + install + author Claude-format skills#577

Open
martinma51 wants to merge 5 commits into
xorbitsai:mainfrom
martinma51:feat/skill-hub
Open

feat(skill-hub): browse + install + author Claude-format skills#577
martinma51 wants to merge 5 commits into
xorbitsai:mainfrom
martinma51:feat/skill-hub

Conversation

@martinma51

Copy link
Copy Markdown
Collaborator

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:

  • Discover — browse ClawHub, the public registry of Claude skills. Featured rail (editorially curated via web/skill_hub_featured.json), "Popular this week" rail, paginated browse-all with sort + debounced search.
  • My Skills — every skill SkillManager sees, tagged builtin / installed / external. Search + sort, remove via ConfirmDialog.
  • Detail / Create / Edit — view any installed skill's SKILL.md + file tree. User-installed ones get an in-browser edit mode (split-pane: textarea + live preview) and a "Create new" page pre-populated with a starter template.

Installs land in <storage_root>/skills/<slug>/ (default ~/.xagent/skills/), the writable root SkillManager already reads from.

Files

frontend/src/app/skill-hub/page.tsx              ← Discover + My Skills tabs
frontend/src/app/skill-hub/[name]/page.tsx       ← detail + edit
frontend/src/app/skill-hub/new/page.tsx          ← create new
frontend/src/components/skill-hub/markdown-editor.tsx
frontend/src/types/skill-hub.ts
frontend/src/lib/sidebar-navigation.ts           ← + Skill Hub entry under Resources
frontend/src/i18n/locales/{en,zh}.ts             ← + nav.skillHub

src/xagent/web/api/skill_hub.py                  ← single router (~1.2K lines, all endpoints)
src/xagent/web/skill_hub_featured.json           ← editorial featured config
src/xagent/web/app.py                            ← register router + startup prewarm

Architecture notes

  • Install policy is a blacklist — refuses ClawHub-flagged malicious / quarantined / revoked. The gate lives server-side, not in the UI.
  • Startup prewarm primes Featured + stats caches in parallel via asyncio.create_task on the existing @app.on_event("startup"). Fire-and-forget; failures are logged but don't block uvicorn.
  • Featured detail fan-out uses asyncio.gather so 6 sequential ~1s ClawHub round-trips become one ~1s parallel batch.
  • sessionStorage SWR cache (5 min TTL matching backend cache) makes re-visits and refreshes feel instant.

ClawHub gotchas (codified in the implementation)

  • sort=trending returns "top N at request limit" rather than paginating — UI shows "Page 1 of 1" on that sort with Next disabled. Default is installsCurrent which paginates over the full ~8k corpus.
  • No total-count endpoint upstream — backend walks cursor pages (capped at 15 × 100 items) to estimate total. Marks truncated=true and renders "Page N of ≥M" when the cap kicks in.
  • No editorial total — featured list is editorial, not algorithmic.

Test plan

  • Sidebar: "Skill Hub" entry appears under Resources, links to /skill-hub.
  • Discover tab: Featured + Popular + browse grid all load. On a cold backend the rails populate within ~3s; on a warm one (after the startup prewarm) ~200ms.
  • Sort dropdown: switching to Newest / Most installed resets to page 1 and updates the total-page count.
  • Pagination: Prev/Next work, page indicator updates, search hides them.
  • Install: pick a ClawHub skill, click Install, watch it appear in My Skills with the "Installed" badge in Discover.
  • Create: write SKILL.md in the in-page editor, save, lands as a new user skill.
  • Edit: open a user-installed skill, change something, save, refresh — change persists.
  • Remove: delete a user skill via the ConfirmDialog, gone after refresh. Builtin / external are protected (delete button hidden).
  • Refresh page on Discover — repaints instantly from sessionStorage, refreshes in background.

Out of scope (deliberate)

  • Skills can't be run standalone from this surface — they're agent capabilities, not user-facing programs. Run a normal task and SkillManager picks the right skill.
  • Currently only ClawHub is wired as a discoverable registry. Source dropdown has placeholders for Hugging Face and the Anthropic Skill catalog; both will need their own backend proxies and are out of scope here.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/xagent/web/api/skill_hub.py Outdated
Comment thread src/xagent/web/api/skill_hub.py Outdated
Comment thread src/xagent/web/api/skill_hub.py Outdated
Comment thread src/xagent/web/api/skill_hub.py Outdated
Comment thread src/xagent/web/api/skill_hub.py Outdated
Comment thread src/xagent/web/api/skill_hub.py Outdated
Comment thread frontend/src/app/skill-hub/[name]/page.tsx Outdated
Comment thread frontend/src/app/skill-hub/[name]/page.tsx Outdated
martinma51 added 3 commits June 1, 2026 18:28
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.
martinma51 added 2 commits June 2, 2026 10:14
…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.
@martinma51

Copy link
Copy Markdown
Collaborator Author

Address gemini-code-assist review feedback (commit 7d9fe1d):

  • Wrap synchronous _clawhub_json and _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.

Verified locally before push:

  • ruff format / ruff check clean
  • mypy src/xagent/web/api/skill_hub.py — no issues
  • pytest tests/integration/test_auto_migration_startup.py tests/web/test_startup_file_storage_sync_wiring.py — 27 passed
  • In-process AsyncClient smoke: DELETE → 404 (no 204-body crash), registry list/featured → 200
  • tsc --noEmit clean on the frontend tree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants