feat: The skill marketplace supports ClawHub China acceleration. (#12396)

This commit is contained in:
CityFun
2026-04-02 06:02:06 +00:00
committed by GitHub
parent 32fed45c1e
commit f5e75d4bfa
14 changed files with 78 additions and 30 deletions
+2 -2
View File
@@ -544,7 +544,7 @@ type AgentConfigFile struct {
type AgentSkillSearchReq struct {
AgentID uint `json:"agentId" validate:"required"`
Source string `json:"source" validate:"required,oneof=clawhub skillhub"`
Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub"`
Keyword string `json:"keyword" validate:"required"`
}
@@ -574,7 +574,7 @@ type AgentSkillUpdateReq struct {
type AgentSkillInstallReq struct {
AgentID uint `json:"agentId" validate:"required"`
Source string `json:"source" validate:"required,oneof=clawhub skillhub"`
Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub"`
Slug string `json:"slug" validate:"required"`
TaskID string `json:"taskID" validate:"required"`
}
+25 -6
View File
@@ -13,6 +13,9 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
)
const clawhubGlobalRegistry = "https://clawhub.com"
const clawhubChinaRegistry = "https://mirror-cn.clawhub.com"
type openclawSkillsList struct {
Skills []openclawSkillListItem `json:"skills"`
}
@@ -74,7 +77,7 @@ func (a AgentService) SearchSkills(req dto.AgentSkillSearchReq) ([]dto.AgentSkil
case "skillhub":
return parseSkillhubSearchResult(output)
default:
return parseClawhubSearchResult(output), nil
return parseClawhubSearchResult(output, req.Source), nil
}
}
@@ -152,7 +155,13 @@ func loadOpenclawSkillSearchOutput(containerName, source, keyword string) (strin
case "skillhub":
return runDockerExecWithStdout(30*time.Second, containerName, "skillhub", "search", keyword, "--json")
default:
return runDockerExecWithStdout(30*time.Second, containerName, "clawhub", "search", keyword)
return runDockerExecWithStdout(
30*time.Second,
containerName,
"sh",
"-c",
fmt.Sprintf("CLAWHUB_REGISTRY=%q clawhub search %q", resolveClawhubRegistry(source), keyword),
)
}
}
@@ -182,7 +191,7 @@ func parseSkillhubSearchResult(output string) ([]dto.AgentSkillSearchItem, error
return items, nil
}
func parseClawhubSearchResult(output string) []dto.AgentSkillSearchItem {
func parseClawhubSearchResult(output, source string) []dto.AgentSkillSearchItem {
lines := strings.Split(strings.TrimSpace(output), "\n")
items := make([]dto.AgentSkillSearchItem, 0, len(lines))
for _, line := range lines {
@@ -194,7 +203,7 @@ func parseClawhubSearchResult(output string) []dto.AgentSkillSearchItem {
Slug: matches[1],
Name: matches[2],
Score: matches[3],
Source: "clawhub",
Source: source,
})
}
return items
@@ -202,10 +211,11 @@ func parseClawhubSearchResult(output string) []dto.AgentSkillSearchItem {
func buildOpenclawSkillInstallCommand(source, slug string) string {
switch source {
case "clawhub":
case "clawhub-global", "clawhub-cn":
return fmt.Sprintf(
"mkdir -p %s && clawhub --workdir /home/node/.openclaw --dir skills install %q",
"mkdir -p %s && CLAWHUB_REGISTRY=%q clawhub --workdir /home/node/.openclaw --dir skills install %q",
openclawManagedSkillsDir,
resolveClawhubRegistry(source),
slug,
)
default:
@@ -218,6 +228,15 @@ func buildOpenclawSkillInstallCommand(source, slug string) string {
}
}
func resolveClawhubRegistry(source string) string {
switch source {
case "clawhub-cn":
return clawhubChinaRegistry
default:
return clawhubGlobalRegistry
}
}
func getOpenclawSkillKey(containerName, name string) (string, error) {
output, err := runDockerExecWithStdout(30*time.Second, containerName, "sh", "-c", fmt.Sprintf("openclaw skills info %q --json 2>&1", name))
if err != nil {
+2 -2
View File
@@ -801,7 +801,7 @@ export namespace AI {
export interface AgentSkillSearchReq {
agentId: number;
source: 'clawhub' | 'skillhub';
source: 'clawhub-global' | 'clawhub-cn' | 'skillhub';
keyword: string;
}
@@ -831,7 +831,7 @@ export namespace AI {
export interface AgentSkillInstallReq {
agentId: number;
source: 'clawhub' | 'skillhub';
source: 'clawhub-global' | 'clawhub-cn' | 'skillhub';
slug: string;
taskID: string;
}
+2 -1
View File
@@ -712,7 +712,8 @@ const message = {
skillsMarket: 'Skill Market',
skillsMarketHint: 'Select a source and search for skills',
skillsMarketEmpty: 'No matching skills found',
skillsMarketSourceClawhub: 'ClawHub (Official)',
skillsMarketSourceClawhubGlobal: 'ClawHub (Global)',
skillsMarketSourceClawhubChina: 'ClawHub (China)',
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
skillsScore: 'Score',
versionUnsupportedTitle: 'This feature is not supported in the current version',
+2 -1
View File
@@ -721,7 +721,8 @@ const message = {
skillsMarket: 'Mercado de habilidades',
skillsMarketHint: 'Seleccione una fuente y busque habilidades',
skillsMarketEmpty: 'No se encontraron habilidades relacionadas',
skillsMarketSourceClawhub: 'ClawHub (Oficial)',
skillsMarketSourceClawhubGlobal: 'ClawHub (Global)',
skillsMarketSourceClawhubChina: 'ClawHub (China)',
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
skillsScore: 'Puntuación',
versionUnsupportedTitle: 'Esta función no es compatible con la versión actual',
+2 -1
View File
@@ -713,7 +713,8 @@ const message = {
skillsMarket: '技能マーケット',
skillsMarketHint: 'ソースを選択して技能を検索してください',
skillsMarketEmpty: '関連する技能が見つかりませんでした',
skillsMarketSourceClawhub: 'ClawHub公式',
skillsMarketSourceClawhubGlobal: 'ClawHubグローバル',
skillsMarketSourceClawhubChina: 'ClawHub中国',
skillsMarketSourceSkillhub: 'SkillHubTencent',
skillsScore: 'スコア',
versionUnsupportedTitle: '現在のバージョンではこの機能はサポートされていません',
+2 -1
View File
@@ -705,7 +705,8 @@ const message = {
skillsMarket: '기술 마켓',
skillsMarketHint: '소스를 선택하고 기술을 검색하세요',
skillsMarketEmpty: '관련 기술을 찾을 없습니다',
skillsMarketSourceClawhub: 'ClawHub (공식)',
skillsMarketSourceClawhubGlobal: 'ClawHub (글로벌)',
skillsMarketSourceClawhubChina: 'ClawHub (중국)',
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
skillsScore: '점수',
versionUnsupportedTitle: '현재 버전에서는 기능을 지원하지 않습니다',
+2 -1
View File
@@ -720,7 +720,8 @@ const message = {
skillsMarket: 'Pasaran kemahiran',
skillsMarketHint: 'Pilih sumber dan cari kemahiran',
skillsMarketEmpty: 'Tiada kemahiran berkaitan ditemui',
skillsMarketSourceClawhub: 'ClawHub (Rasmi)',
skillsMarketSourceClawhubGlobal: 'ClawHub (Global)',
skillsMarketSourceClawhubChina: 'ClawHub (China)',
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
skillsScore: 'Skor',
versionUnsupportedTitle: 'Ciri ini tidak disokong dalam versi semasa',
+2 -1
View File
@@ -715,7 +715,8 @@ const message = {
skillsMarket: 'Mercado de habilidades',
skillsMarketHint: 'Selecione uma fonte e pesquise habilidades',
skillsMarketEmpty: 'Nenhuma habilidade correspondente encontrada',
skillsMarketSourceClawhub: 'ClawHub (Oficial)',
skillsMarketSourceClawhubGlobal: 'ClawHub (Global)',
skillsMarketSourceClawhubChina: 'ClawHub (China)',
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
skillsScore: 'Pontuação',
versionUnsupportedTitle: 'Este recurso não é compatível com a versão atual',
+2 -1
View File
@@ -712,7 +712,8 @@ const message = {
skillsMarket: 'Рынок навыков',
skillsMarketHint: 'Выберите источник и выполните поиск навыков',
skillsMarketEmpty: 'Подходящие навыки не найдены',
skillsMarketSourceClawhub: 'ClawHub (Официальный)',
skillsMarketSourceClawhubGlobal: 'ClawHub (Глобальный)',
skillsMarketSourceClawhubChina: 'ClawHub (Китай)',
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
skillsScore: 'Оценка',
versionUnsupportedTitle: 'Эта функция не поддерживается в текущей версии',
+2 -1
View File
@@ -716,7 +716,8 @@ const message = {
skillsMarket: 'Yetenek pazarı',
skillsMarketHint: 'Bir kaynak seçin ve yetenek arayın',
skillsMarketEmpty: 'Eşleşen yetenek bulunamadı',
skillsMarketSourceClawhub: 'ClawHub (Resmi)',
skillsMarketSourceClawhubGlobal: 'ClawHub (Global)',
skillsMarketSourceClawhubChina: 'ClawHub (Çin)',
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
skillsScore: 'Puan',
versionUnsupportedTitle: 'Bu özellik mevcut sürümde desteklenmiyor',
+2 -1
View File
@@ -678,7 +678,8 @@ const message = {
skillsMarket: '技能市場',
skillsMarketHint: '請選擇來源並搜尋技能',
skillsMarketEmpty: '未找到相關技能',
skillsMarketSourceClawhub: 'ClawHub官方',
skillsMarketSourceClawhubGlobal: 'ClawHub全球',
skillsMarketSourceClawhubChina: 'ClawHub中國',
skillsMarketSourceSkillhub: 'SkillHub騰訊',
skillsScore: '評分',
versionUnsupportedTitle: '當前版本暫不支援該功能',
+2 -1
View File
@@ -677,7 +677,8 @@ const message = {
skillsMarket: '技能市场',
skillsMarketHint: '请选择来源并搜索技能',
skillsMarketEmpty: '未找到相关技能',
skillsMarketSourceClawhub: 'ClawHub官方',
skillsMarketSourceClawhubGlobal: 'ClawHub全球',
skillsMarketSourceClawhubChina: 'ClawHub中国',
skillsMarketSourceSkillhub: 'SkillHub腾讯',
skillsScore: '评分',
versionUnsupportedTitle: '当前版本暂不支持该功能',
@@ -1,9 +1,9 @@
<template>
<VersionSupport v-if="!supported" :min-version="openclawMinSupportedVersion" />
<div v-else>
<el-radio-group v-model="mode" class="view-switch">
<el-radio-button label="installed">{{ t('app.installed') }}</el-radio-button>
<el-radio-group v-model="mode" class="view-switch" @change="handleModeChange">
<el-radio-button label="market">{{ t('aiTools.agents.skillsMarket') }}</el-radio-button>
<el-radio-button label="installed">{{ t('app.installed') }}</el-radio-button>
</el-radio-group>
<div v-loading="loading" class="skills-content">
@@ -25,7 +25,11 @@
</template>
<template v-else>
<el-select v-model="marketSource" class="p-w-200" @change="handleMarketSourceChange">
<el-option :value="'clawhub'" :label="t('aiTools.agents.skillsMarketSourceClawhub')" />
<el-option
:value="'clawhub-global'"
:label="t('aiTools.agents.skillsMarketSourceClawhubGlobal')"
/>
<el-option :value="'clawhub-cn'" :label="t('aiTools.agents.skillsMarketSourceClawhubChina')" />
<el-option :value="'skillhub'" :label="t('aiTools.agents.skillsMarketSourceSkillhub')" />
</el-select>
<el-input
@@ -150,7 +154,7 @@ import VersionSupport from './components/version-support.vue';
type SkillGroupKey = 'builtIn' | 'external' | 'workspace' | 'extra' | 'other';
type SkillViewMode = 'installed' | 'market';
type SkillMarketSource = 'clawhub' | 'skillhub';
type SkillMarketSource = 'clawhub-global' | 'clawhub-cn' | 'skillhub';
const openclawMinSupportedVersion = '2026.3.23';
const props = defineProps<{
@@ -161,10 +165,11 @@ const { t } = useI18n();
const { isIntl } = useGlobalStore();
const loading = ref(false);
const searching = ref(false);
const mode = ref<SkillViewMode>('installed');
const mode = ref<SkillViewMode>('market');
const installedKeyword = ref('');
const marketKeyword = ref('');
const marketSource = ref<SkillMarketSource>(isIntl.value ? 'clawhub' : 'skillhub');
const getDefaultMarketSource = (): SkillMarketSource => (isIntl.value ? 'clawhub-global' : 'clawhub-cn');
const marketSource = ref<SkillMarketSource>(getDefaultMarketSource());
const marketSearched = ref(false);
const agentId = ref(0);
const skills = ref<AI.AgentSkillItem[]>([]);
@@ -172,6 +177,7 @@ const marketResults = ref<AI.AgentSkillSearchItem[]>([]);
const updatingSkill = ref('');
const installingSkill = ref('');
const taskLogRef = ref<InstanceType<typeof TaskLog>>();
const installedLoaded = ref(false);
const supported = computed(() => isOpenclawCurrentHTTPVersion(props.appVersion));
const groupTagLabels = computed<Record<SkillGroupKey, string>>(() => ({
@@ -251,6 +257,7 @@ const loadSkills = async () => {
try {
const res = await listAgentSkills({ agentId: agentId.value });
skills.value = res.data || [];
installedLoaded.value = true;
} finally {
loading.value = false;
}
@@ -279,13 +286,21 @@ const handleMarketSourceChange = () => {
marketSearched.value = false;
};
const handleModeChange = async (value: SkillViewMode) => {
if (value !== 'installed' || installedLoaded.value) {
return;
}
await loadSkills();
};
const load = async (id: number) => {
agentId.value = id;
mode.value = 'installed';
marketSource.value = isIntl.value ? 'clawhub' : 'skillhub';
mode.value = 'market';
marketSource.value = getDefaultMarketSource();
marketResults.value = [];
marketSearched.value = false;
await loadSkills();
skills.value = [];
installedLoaded.value = false;
};
const toggleSkill = async (skill: AI.AgentSkillItem, enabled: boolean) => {
@@ -326,7 +341,11 @@ const installSkill = async (skill: AI.AgentSkillSearchItem) => {
};
const handleTaskClose = async () => {
await loadSkills();
if (mode.value === 'installed') {
await loadSkills();
return;
}
installedLoaded.value = false;
};
defineExpose({