Skip to content

feat(skills): per-profile skills toggle in Skills screen - #429

Closed
cypres0099 wants to merge 1 commit into
outsourc-e:mainfrom
cypres0099:feat/per-profile-skills-toggle
Closed

feat(skills): per-profile skills toggle in Skills screen#429
cypres0099 wants to merge 1 commit into
outsourc-e:mainfrom
cypres0099:feat/per-profile-skills-toggle

Conversation

@cypres0099

Copy link
Copy Markdown

Summary

Adds a Profile dropdown to the Skills screen so one workspace process can manage skills.disabled across every installed Hermes profile. Today the screen only edits the profile the dashboard daemon is bound to; toggling a sub-profile (scratch-worker, writer, emailtriage, …) requires running a second daemon.

Pairs with NousResearch/hermes-agent#25116, which adds the matching server-side endpoints on the dashboard:

  • GET /api/profiles/{name}/skills
  • PUT /api/profiles/{name}/skills/toggle
  • is_active flag on /api/profiles

What changes

Backend

Two thin proxy routes that forward to the new dashboard endpoints:

Workspace route Dashboard route
GET /api/profiles/skills?name=<profile> GET /api/profiles/<profile>/skills
PUT /api/profiles/toggle-skill body {profile,name,enabled} PUT /api/profiles/<profile>/skills/toggle

Both go through the existing dashboardFetch helper, so auth is transparent. They early-return when capabilities.dashboard.available is false, so older zero-fork/managed-gateway setups don't get spurious 5xxs.

I did not add a new /api/profiles proxy — the existing /api/profiles/list already surfaces which profile the workspace is bound to (active/activeProfile), which is the same source-of-truth the dashboard uses to compute is_active. Reusing it avoids a redundant network hop on every Skills mount.

Frontend (src/screens/skills/skills-screen.tsx)

  • New Profile <select> in the Skills toolbar. Hidden when only one profile is installed (single-profile parity with the dashboard's sidebar). Defaults to the active profile, falls back to is_default, then any.
  • The active profile is suffixed (active) so the user always knows which profile the daemon is currently bound to — mirrors the dashboard PR's affordance.
  • When the user picks a non-active profile:
    • The screen fetches /api/profiles/skills?name=<profile> instead of /api/skills.
    • The lighter per-profile payload is normalized into SkillSummary so existing rows/dialogs render unchanged.
    • The Marketplace tab and Origin filter are hidden — both depend on workspace-side enrichment (Skills Hub, bundled manifest, agent-created flags) that the dashboard's per-profile endpoint deliberately omits.
    • Toggle writes go through PUT /api/profiles/toggle-skill so skills.disabled is mutated in the target profile's config.yaml, not the active one.
    • Install/uninstall on a non-active profile is intentionally blocked with a friendly error pointing the user back to the active profile — the dashboard's per-profile endpoint only supports toggle today.

How to test

  1. Run a dashboard daemon with at least one sub-profile in ~/.hermes/profiles/ (e.g. scratch-worker, writer).
  2. Start the workspace and open the Skills screen. The Profile dropdown appears in the toolbar, defaulted to the active profile with (active) suffix.
  3. Switch to a non-active profile. The list refreshes from that profile's skills/ directory; Marketplace tab and Origin filter disappear.
  4. Toggle any skill. Reload the page and verify skills.disabled in ~/.hermes/profiles/<name>/config.yaml reflects the change, and the active profile's config.yaml is untouched.

I smoke-tested this against two co-located Hermes installs:

  • Iris (default + emailtriage + hivemind-community-manager) — toggling ascii-art off on emailtriage from the workspace UI added ascii-art to ~/.hermes/profiles/emailtriage/config.yaml only; ~/.hermes/config.yaml was unchanged.
  • Artemis (default + scratch-worker + writer + analyst + creative + researcher + shopify-admin) — toggling airtable on scratch-worker removed airtable from scratch-worker/config.yaml only; default's disabled list was unchanged.

Known limitations

  • The per-profile list excludes skills loaded via skills.external_dirs — same scope as the dashboard's endpoint.
  • Install/uninstall on a non-active profile requires either a follow-up to dashboard PR #25116 or running a daemon bound to that profile.

🤖 Generated with Claude Code

Pairs with NousResearch/hermes-agent#25116.

Adds two thin proxy routes that forward to the dashboard's new
profile-scoped skills endpoints (introduced upstream by the dashboard
PR linked above), plus a Profile dropdown in the Skills screen
toolbar that scopes the view to one installed profile at a time.

Backend
- src/routes/api/profiles/skills.ts
  GET ?name=<profile> -> dashboard GET /api/profiles/<profile>/skills
- src/routes/api/profiles/toggle-skill.ts
  PUT body {profile,name,enabled} -> dashboard PUT /api/profiles/<profile>/skills/toggle
- Existing /api/profiles/list already exposes which profile the
  dashboard process is bound to ("active"), so we reuse it for the
  dropdown source rather than adding a redundant proxy for the
  dashboard's /api/profiles.

Frontend
- New Profile <select> in the Skills toolbar, hidden when only one
  profile is installed (single-profile parity with the dashboard
  Skills sidebar). Defaults to the active profile, falls back to
  is_default, then any.
- When the user picks a non-active profile, the screen:
  - fetches /api/profiles/skills?name=<profile> instead of /api/skills
  - normalizes the lighter per-profile payload into SkillSummary
    so existing rows/dialogs still render
  - forces the 'installed' tab and hides 'marketplace' (the dashboard
    endpoint only enumerates installed skills inside that profile's
    own skills/ dir)
  - hides the "Origin" filter, which depends on workspace-side
    enrichment (bundled manifest, agent-created markers) the dashboard
    endpoint does not return
  - routes toggle writes through PUT /api/profiles/toggle-skill so the
    edit lands in the target profile's config.yaml, not the active one
  - early-returns on install/uninstall with a clear "switch to the
    active profile" message — neither the dashboard nor the workspace
    can safely install into a non-resident profile via the legacy
    /api/skills/{install,uninstall} routes today

UX notes
- Mirrors the dashboard PR's "(active)" suffix in the dropdown so the
  user always knows which profile the daemon is currently bound to.
- The toggle is the same Switch users already use on the active
  profile; the only difference is the destination of the write.

How to test
1. Run a dashboard daemon with at least one sub-profile in
   ~/.hermes/profiles/ (e.g. scratch-worker, writer).
2. Open the workspace Skills screen.
3. The Profile dropdown appears in the toolbar with all installed
   profiles. Active is marked "(active)".
4. Switch to a non-active profile -> the list refreshes from that
   profile's skills/ dir. Marketplace tab and Origin filter disappear.
5. Toggle any skill -> reload, verify skills.disabled in the target
   profile's config.yaml (not the active profile's).

Known limitations
- Install/uninstall on a non-active profile is intentionally blocked;
  the dashboard's per-profile endpoint only supports toggle. A follow-
  up to dashboard PR #25116 could lift this.
- The per-profile list does not include skills loaded via
  skills.external_dirs — same scope as the dashboard's endpoint.
@Cossackx

Copy link
Copy Markdown
Contributor

Hermes verification: mergeable; local build previously passed for this 4-file change. GitHub reports no checks on the branch. Direct squash merge was attempted but blocked by GitHub permissions for Cossackx (MergePullRequest). Maintainer action needed: squash-merge if the paired Hermes Agent dashboard endpoint is available.

@cypres0099

Copy link
Copy Markdown
Author

Closing as superseded by the current Hermes Agent main implementation of profile-scoped skills/toolsets management. This workspace-side branch targeted the older companion endpoint shape, so reviving it would need a fresh branch against the current upstream API instead of merging this PR.

@cypres0099 cypres0099 closed this Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants