Skip to content
This repository was archived by the owner on Jul 27, 2026. It is now read-only.

feat(skills): default skills, installed CRUD, marketplace install, and conversation wiring - #486

Closed
malhotra5 wants to merge 6 commits into
mainfrom
feat/skills-crud-and-default-skills
Closed

malhotra5 wants to merge 6 commits into
mainfrom
feat/skills-crud-and-default-skills

Conversation

@malhotra5

@malhotra5 malhotra5 commented May 15, 2026 •

Copy link
Copy Markdown
Member

Summary

Closes agent-canvas issue #2276. Integrates typescript-client PR #166 (SkillsClient CRUD methods) and establishes a clean end-to-end skills flow: from what appears on the Settings page, through user configuration, to what the agent receives when a conversation starts.


Design: Default Skills vs. Full Marketplace

Default skills are a system concern, not a user preference.

Concept Owner Mechanism
Default skills System-curated static list public/default-skills-marketplace.json + marketplace_path on every getSkills call
Installed skills User — explicit install POST /api/skills/install → stored in ~/.openhands/skills/installed/
Project skills Repo — committed files .agents/skills/ in working directory
Full marketplace User-opt-in only Browse via GET /api/skills/marketplace; install individually

The full 44+ OpenHands/extensions marketplace cache on the server is never loaded by default. All getSkills calls pair load_public: true with marketplace_path pointing at the curated manifest — the server uses this as a scoping filter, returning only those 4 skills from its public cache.


New / Changed Files

New

File Purpose
public/default-skills-marketplace.json System-curated manifest: github, code-review, docker, openhands-automation — served as a static asset
src/services/default-skills.ts DEFAULT_SKILL_NAMES (badge driver) and DEFAULT_MARKETPLACE_PATH constant

Removed

  • src/api/skills-preferences-store.ts — deleted
  • default_skills field removed from Settings type, DEFAULT_SETTINGS, use-settings normalizer, both settings-service API files
  • Default Skills chips section in skills-settings.tsx — the user-editable panel, Reset to Recommended button, and associated state/handlers
  • Stale i18n keys: SETTINGS$SKILLS_DEFAULT_TITLE, SETTINGS$SKILLS_DEFAULT_DESCRIPTION, SETTINGS$SKILLS_RESET_TO_RECOMMENDED
  • VITE_LOAD_PUBLIC_SKILLS env-var gate (replaced by always-on marketplace-scoped loading)

Updated

src/api/skills-service.ts

  • getSkills() always passes load_public: true + marketplace_path — settings page shows only curated defaults + user-installed + project skills, never the full 44+ cache
  • Added CRUD methods via SkillsClient: listInstalledSkills(), installSkill(source), uninstallSkill(name), toggleInstalledSkill(name, enabled), getMarketplace()

src/api/agent-server-adapter.ts

  • createAgentFromSettings — removed untyped load_public_skills / load_user_skills / marketplace_path from agent_context (not in the AgentContext SDK type; server ignores them via open index signature); replaced with agent_context.skills: SkillInfo[] — the only typed skill-injection field
  • buildStartConversationRequestWithEncryptedSettings — fetches skills in parallel with encrypted settings and secrets using the same load_public: true + marketplace_path flags as the settings page, filters out settings.disabled_skills, embeds result as agent_context.skills. What you see in Settings is exactly what the agent gets.
  • loadSkillsForConversation — uses the same flags for consistency with the settings page
  • Added defaultMarketplaceUrl() helper — single construction point for the absolute manifest URL
  • Added skills?: SdkSkillInfo[] to StartConversationOptions

src/routes/skills-settings.tsx

  • DEFAULT badge driven by DEFAULT_SKILL_NAMES.includes(skill.name) (static, not user-controlled)
  • Added installed skills CRUD UI and marketplace install UI wired to the new service methods

agent_context.skills rationale: The AgentContext SDK type exposes skills?: unknown[] as the only typed field for passing skill content to a conversation. Pre-fetching skills client-side, applying disabled_skills filtering, and embedding the full SkillInfo[] (with content) is the only spec-compliant approach that also honours user preferences.


End-to-End Skills Flow

  1. Settings page loads — GET /api/skills with load_public: true + marketplace_path + load_user: true + load_project: true → returns curated 4 + user-installed + project skills; DEFAULT badge shown for names in DEFAULT_SKILL_NAMES
  2. User enables / disables a skill — stored in settings.disabled_skills[]; no server call
  3. User installs a skill — POST /api/skills/install { source } → server clones to ~/.openhands/skills/installed/; appears on next page load via load_user: true
  4. User uninstalls / refreshes — DELETE /api/skills/installed/{name} / POST /api/skills/installed/{name}/refresh
  5. New conversation starts — buildStartConversationRequestWithEncryptedSettings fetches skills (same flags), filters disabled_skills, sets agent_context.skills in CreateConversationRequest
  6. Active conversation skills panel — loadSkillsForConversation uses same flags → consistent with settings page
  7. Full marketplace browsing — GET /api/skills/marketplace (separate TTL-cached endpoint); user installs skills individually from there

Tests

  • 2,305+ tests passing (319/321 test files; 2 skipped)
  • agent-server-adapter.test.ts — agent_context assertion updated: sync builder produces {}; stale shouldLoadPublicSkills mock removed
  • skills-service.test.ts — asserts marketplace_path is always included in getSkills requests
  • skills-settings.test.tsx — chip/default_skills tests replaced with static DEFAULT-badge tests

This PR was created and updated by an AI agent (OpenHands) on behalf of the team.

…tall UI

Closes #380. Depends on typescript-client PR #166 (SkillsClient CRUD methods)
which targets agent-server PR OpenHands/software-agent-sdk#3231.

Changes:
- Bump @openhands/typescript-client to main SHA 306f121 (picks up SkillsClient
  installSkill / uninstallSkill / toggleSkill / listInstalledSkills /
  getMarketplace methods)
- Settings type: add default_skills?: string[]
- services/settings: export CURATED_DEFAULT_SKILLS; seed DEFAULT_SETTINGS
  with disabled_skills:[] and default_skills:CURATED_DEFAULT_SKILLS
- skills-preferences-store: localStorage helper for default_skills
- settings-service (local + cloud): persist/restore default_skills
- use-settings hook: map default_skills from API response
- skills-service: full CRUD — listInstalledSkills, installSkill,
  uninstallSkill, toggleInstalledSkill, getMarketplace (all typed against
  SkillsClient, no casts)
- use-installed-skills query hook (InstalledSkillSummary[])
- use-install-skill / use-uninstall-skill mutation hooks
- i18n: add SETTINGS$SKILLS_DEFAULT_*, SETTINGS$SKILLS_INSTALL_*,
  SETTINGS$SKILLS_INSTALLED_* keys; regenerate i18n-keys enum
- skill-card: isDefault / isInstalled props; DEFAULT + INSTALLED badges with
  data-testid attributes
- skills-settings route: rewrite with three sections —
  (1) Default Skills chips + Reset-to-recommended button (persisted via
      useSaveSettings on every toggle),
  (2) Installed Skills list with version badge + Uninstall button,
  (3) Marketplace / install-from-source input
- skills-handlers MSW mock: mutable store for installedSkills; handlers for
  GET /api/skills/installed, POST /api/skills/install,
  GET/PATCH/DELETE /api/skills/installed/:name, GET /api/skills/marketplace
- Register SKILLS_HANDLERS in handlers.ts
- skills-settings.test: 20 tests covering all new behaviours (default badge,
  chip toggle, reset, installed section, marketplace input enable/disable)

Co-authored-by: openhands <openhands@all-hands.dev>
@vercel

vercel Bot commented May 15, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agent-canvas Ready Ready Preview, Comment May 15, 2026 7:33pm

Request Review

…em-curated static list

- Add public/default-skills-marketplace.json: system-curated skill manifest
  (github, code-review, docker)
- Add src/services/default-skills.ts: DEFAULT_SKILL_NAMES constant (drives
  DEFAULT badge client-side) and DEFAULT_MARKETPLACE_PATH ('/default-skills-marketplace.json')
- Remove CURATED_DEFAULT_SKILLS array and default_skills field from
  DEFAULT_SETTINGS in services/settings.ts
- Remove default_skills from Settings interface (types/settings.ts)
- Delete skills-preferences-store.ts (no longer needed)
- Strip all default_skills persistence/propagation from settings-service.api.ts
  and cloud/settings-service.api.ts
- Remove default_skills mapping from use-settings normalizer
- Rewrite skills-settings.tsx: remove chips section, reset button, and
  defaultSet state; drive DEFAULT badge via DEFAULT_SKILL_NAMES.includes()
- Remove stale i18n keys: SKILLS_DEFAULT_TITLE, SKILLS_DEFAULT_DESCRIPTION,
  SKILLS_RESET_TO_RECOMMENDED
- Wire marketplace_path into loadSkillsForConversation and createAgentFromSettings
  (used when VITE_LOAD_PUBLIC_SKILLS=true to scope public skill loading to the
  curated list instead of all 44+ marketplace skills)
- Update __tests__/routes/skills-settings.test.tsx: remove chip/default_skills
  tests, add badge tests driven by static DEFAULT_SKILL_NAMES
- Update __tests__/api/agent-server-adapter.test.ts: expect marketplace_path
  in agent_context when load_public=true

All 2,305+ tests pass.

Co-authored-by: openhands <openhands@all-hands.dev>
Adds the openhands-automation skill to both
public/default-skills-marketplace.json and DEFAULT_SKILL_NAMES so it
shows the DEFAULT badge on the Skills settings page and is included in
the curated public skill load when VITE_LOAD_PUBLIC_SKILLS=true.

Co-authored-by: openhands <openhands@all-hands.dev>
…on start

Previously:
- Skills settings page: always fetched with load_public:true (full catalog)
- Conversation creation: sent untyped load_public_skills/load_user_skills/
  marketplace_path flags in agent_context — not in the AgentContext type,
  effectively ignored by the server. disabled_skills was never forwarded.
  VITE_LOAD_PUBLIC_SKILLS defaulted to false → agents got zero skills.

Now:
- buildStartConversationRequestWithEncryptedSettings fetches skills in
  parallel (same load_public/load_user/load_project flags as the settings
  page), filters by settings.disabled_skills, and passes the result as the
  typed agent_context.skills field.
- loadSkillsForConversation also uses load_public:true unconditionally.
- Removed the VITE_LOAD_PUBLIC_SKILLS env-var gate and marketplace_path
  scoping from the conversation path — no longer needed.
- Tests updated accordingly.

Co-authored-by: openhands <openhands@all-hands.dev>
Previously load_public:true with no marketplace_path loaded all 44+
skills from the full cached OpenHands/extensions clone on the server.

Now all three getSkills call sites (settings page, conversation builder,
loadSkillsForConversation) always pair load_public:true with
marketplace_path pointing at /default-skills-marketplace.json.

The server uses marketplace_path as a scoping filter on load_public — so
only the 4 curated skills (github, code-review, docker,
openhands-automation) are loaded as "public" skills. User-installed and
project skills are unaffected (load_user/load_project still set).

To load more skills users install them explicitly via the CRUD endpoints
(POST /api/skills/install). Browsing the full 44+ catalog is handled
separately via getMarketplace().

Co-authored-by: openhands <openhands@all-hands.dev>
@malhotra5 malhotra5 changed the title feat(skills): default skills settings, installed CRUD, and marketplace install UI feat(skills): default skills, installed CRUD, marketplace install, and conversation wiring May 15, 2026
marketplace_path is a relative filesystem path inside the server's cloned
extensions git repo (~/.openhands/cache/skills/), NOT a URL. Passing
window.location.origin + '/default-skills-marketplace.json' caused the
server to look for that string as a path within its repo, fail to find it,
and return [] for all public skills — breaking the skills page entirely.

Server behaviour (from skill.py):
- marketplace_path == DEFAULT_MARKETPLACE_PATH ('marketplaces/default.json')
  and file not found → falls back to loading all public skills
- marketplace_path != DEFAULT and file not found → returns [] (our bug)
- marketplace_path == null → loads all public skills unconditionally

Note: 'marketplaces/default.json' does not currently exist in
OpenHands/extensions, so the server always falls back to loading all
public skills when no override is provided.

The DEFAULT badge on skill cards is still driven client-side by
DEFAULT_SKILL_NAMES; public/default-skills-marketplace.json is retained
as a frontend asset (for future install-flow use) but is not a mechanism
for server-side skill scoping.

Co-authored-by: openhands <openhands@all-hands.dev>

This branch was successfully deployed

1 active deployment
Preview — ca78fc34 Deployed May 15, 2026 by vercel[bot]
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants