@@ -962,11 +962,6 @@ def _load_marketplace_object(
962962 return None
963963
964964
965- def _is_remote_source (source : str ) -> bool :
966- """Check if a resolved source string is remote (needs fetching)."""
967- return source .startswith (("github:" , "https://" , "http://" , "git@" , "git://" ))
968-
969-
970965def _load_skills_from_resolved_path (
971966 plugin_dir : Path ,
972967 skill_base_dir : Path | None = None ,
@@ -975,25 +970,16 @@ def _load_skills_from_resolved_path(
975970
976971 Tries to load as a full plugin first (has skills/ subdir), then
977972 falls back to loading as a single skill directory (has SKILL.md).
978-
979- Args:
980- plugin_dir: Path to the resolved plugin or skill directory.
981- skill_base_dir: Base directory for computing relative skill source paths.
982-
983- Returns:
984- List of loaded Skill objects (may be empty).
985973 """
986974 if not plugin_dir .is_dir ():
987975 return []
988976
989- # If it has a skills/ subdirectory, load skills from it (plugin structure)
990977 inner_skills_dir = plugin_dir / "skills"
991978 if inner_skills_dir .is_dir ():
992979 from openhands .sdk .plugin .plugin import _load_skills
993980
994981 return _load_skills (plugin_dir )
995982
996- # If the directory itself contains a SKILL.md, load as a single skill
997983 md_path = find_skill_md (plugin_dir )
998984 if md_path :
999985 try :
@@ -1018,21 +1004,17 @@ def _resolve_plugin_skills(
10181004 """Resolve a marketplace plugin entry's source and load its skills.
10191005
10201006 Resolution order:
1021- 1. Resolve the source field via Marketplace.resolve_plugin_source()
1022- 2. For remote sources: fetch the plugin repo, then load skills from it
1023- 3. For local sources that exist: load skills directly
1024- 4. Fall back to name-based lookup in skills_dir (backward compatibility)
1025-
1026- Args:
1027- marketplace: The Marketplace object (for source resolution).
1028- plugin_entry: The plugin entry to resolve.
1029- skills_dir: The skills/ directory in the extensions repo.
1030- repo_path: Root path of the extensions repo.
1031-
1032- Returns:
1033- List of Skill objects loaded from the plugin.
1007+ 1. Resolve source via Marketplace.resolve_plugin_source()
1008+ 2. Remote sources: fetch via Plugin.fetch(), then load skills
1009+ 3. Local sources: load skills directly from the path
1010+ 4. Fallback: name-based lookup in skills_dir (backward compatibility)
10341011 """
1012+ from openhands .sdk .plugin import Plugin , PluginFetchError
1013+
10351014 # Try to resolve the source field
1015+ source : str | None = None
1016+ ref : str | None = None
1017+ subpath : str | None = None
10361018 try :
10371019 source , ref , subpath = marketplace .resolve_plugin_source (plugin_entry )
10381020 except Exception as e :
@@ -1041,113 +1023,63 @@ def _resolve_plugin_skills(
10411023 plugin_entry .name ,
10421024 e ,
10431025 )
1044- return _fallback_skill_lookup (plugin_entry .name , skills_dir , repo_path )
10451026
10461027 # Remote source: fetch and load
1047- if _is_remote_source (source ):
1048- return _fetch_and_load_remote_skills (source , ref , subpath , plugin_entry .name )
1049-
1050- # Local source: try to load from the resolved path
1051- local_path = Path (source )
1052- if local_path .is_dir ():
1053- skills = _load_skills_from_resolved_path (local_path , skill_base_dir = repo_path )
1054- if skills :
1055- return skills
1056-
1057- # Fall back to name-based lookup in skills/ directory
1058- return _fallback_skill_lookup (plugin_entry .name , skills_dir , repo_path )
1059-
1060-
1061- def _fetch_and_load_remote_skills (
1062- source : str ,
1063- ref : str | None ,
1064- subpath : str | None ,
1065- plugin_name : str ,
1066- ) -> list [Skill ]:
1067- """Fetch a remote plugin and load skills from it.
1068-
1069- Args:
1070- source: Remote source string (e.g. "github:owner/repo").
1071- ref: Optional git ref (branch, tag, commit).
1072- subpath: Optional subdirectory within the fetched repo.
1073- plugin_name: Plugin name (for logging).
1074-
1075- Returns:
1076- List of loaded Skill objects.
1077- """
1078- from openhands .sdk .plugin import Plugin , PluginFetchError
1079-
1080- try :
1081- plugin_path = Plugin .fetch (source = source , ref = ref , repo_path = subpath )
1082- except PluginFetchError as e :
1083- logger .warning (
1084- "Failed to fetch plugin '%s' from %s: %s" ,
1085- plugin_name ,
1086- source ,
1087- e ,
1088- )
1089- return []
1090-
1091- # Try loading as a full plugin first
1092- try :
1093- plugin = Plugin .load (plugin_path )
1094- skills = plugin .get_all_skills ()
1095- if skills :
1096- logger .debug (
1097- "Loaded %d skills from plugin '%s' (%s)" ,
1098- len (skills ),
1099- plugin_name ,
1028+ if source and source .startswith (
1029+ ("github:" , "https://" , "http://" , "git@" , "git://" )
1030+ ):
1031+ try :
1032+ plugin_path = Plugin .fetch (source = source , ref = ref , repo_path = subpath )
1033+ except PluginFetchError as e :
1034+ logger .warning (
1035+ "Failed to fetch plugin '%s' from %s: %s" ,
1036+ plugin_entry .name ,
11001037 source ,
1038+ e ,
11011039 )
1102- return skills
1103- except Exception :
1104- pass
1105-
1106- # Fall back to loading skills directly from the directory
1107- return _load_skills_from_resolved_path (plugin_path )
1040+ return []
11081041
1042+ # Try loading as a full plugin, fall back to raw directory
1043+ try :
1044+ plugin = Plugin .load (plugin_path )
1045+ skills = plugin .get_all_skills ()
1046+ if skills :
1047+ return skills
1048+ except Exception :
1049+ pass
1050+ return _load_skills_from_resolved_path (plugin_path )
11091051
1110- def _fallback_skill_lookup (
1111- skill_name : str , skills_dir : Path , repo_path : Path
1112- ) -> list [Skill ]:
1113- """Look up a skill by name in the skills directory (backward compat).
1114-
1115- Checks for SKILL.md in a subdirectory, then legacy .md files.
1116-
1117- Args:
1118- skill_name: Name of the skill to look up.
1119- skills_dir: The skills/ directory to search in.
1120- repo_path: Root path of the repo (for Skill.load base_dir).
1121-
1122- Returns:
1123- List containing the loaded Skill, or empty list if not found.
1124- """
1125- skill_md = skills_dir / skill_name / "SKILL.md"
1126- if skill_md .exists ():
1127- return _try_load_skill_file (skill_md , repo_path )
1052+ # Local source: try to load from the resolved path
1053+ if source :
1054+ local_path = Path (source )
1055+ if local_path .is_dir ():
1056+ skills = _load_skills_from_resolved_path (
1057+ local_path , skill_base_dir = repo_path
1058+ )
1059+ if skills :
1060+ return skills
11281061
1129- legacy_md = skills_dir / f"{ skill_name } .md"
1130- if legacy_md .exists ():
1131- return _try_load_skill_file (legacy_md , repo_path )
1062+ # Fall back to name-based lookup in skills/ directory
1063+ for candidate in (
1064+ skills_dir / plugin_entry .name / "SKILL.md" ,
1065+ skills_dir / f"{ plugin_entry .name } .md" ,
1066+ ):
1067+ if candidate .exists ():
1068+ try :
1069+ skill = Skill .load (path = candidate , skill_base_dir = repo_path )
1070+ if skill is not None :
1071+ return [skill ]
1072+ except Exception as e :
1073+ logger .warning (f"Failed to load skill from { candidate .name } : { e } " )
1074+ return []
11321075
11331076 logger .debug (
11341077 "Skill '%s' not found in skills dir via fallback lookup" ,
1135- skill_name ,
1078+ plugin_entry . name ,
11361079 )
11371080 return []
11381081
11391082
1140- def _try_load_skill_file (skill_file : Path , repo_path : Path ) -> list [Skill ]:
1141- """Try to load a single skill file, returning it in a list or empty."""
1142- try :
1143- skill = Skill .load (path = skill_file , skill_base_dir = repo_path )
1144- if skill is not None :
1145- return [skill ]
1146- except Exception as e :
1147- logger .warning (f"Failed to load skill from { skill_file .name } : { e } " )
1148- return []
1149-
1150-
11511083def load_public_skills (
11521084 repo_url : str = PUBLIC_SKILLS_REPO ,
11531085 branch : str = PUBLIC_SKILLS_BRANCH ,
@@ -1250,10 +1182,13 @@ def load_public_skills(
12501182 )
12511183
12521184 for skill_file in all_skill_files :
1253- loaded = _try_load_skill_file (skill_file , repo_path )
1254- for skill in loaded :
1255- all_skills .append (skill )
1256- logger .debug (f"Loaded public skill: { skill .name } " )
1185+ try :
1186+ skill = Skill .load (path = skill_file , skill_base_dir = repo_path )
1187+ if skill is not None :
1188+ all_skills .append (skill )
1189+ logger .debug (f"Loaded public skill: { skill .name } " )
1190+ except Exception as e :
1191+ logger .warning (f"Failed to load skill from { skill_file .name } : { e } " )
12571192
12581193 except Exception as e :
12591194 logger .warning (f"Failed to load public skills from { repo_url } : { e !s} " )
0 commit comments