diff --git a/agent/app/api/v2/agents.go b/agent/app/api/v2/agents.go index a8c7b1b079f8..e4d31537ed0e 100644 --- a/agent/app/api/v2/agents.go +++ b/agent/app/api/v2/agents.go @@ -1171,6 +1171,26 @@ func (b *BaseApi) InstallAgentSkill(c *gin.Context) { helper.Success(c) } +// @Tags AI +// @Summary Uninstall Agent skill +// @Accept json +// @Param request body dto.AgentSkillUninstallReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/agents/skills/uninstall [post] +func (b *BaseApi) UninstallAgentSkill(c *gin.Context) { + var req dto.AgentSkillUninstallReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := agentService.UninstallSkill(req); err != nil { + helper.BadRequest(c, err) + return + } + helper.Success(c) +} + // @Tags AI // @Summary Login Agent Weixin channel // @Accept json diff --git a/agent/app/dto/agents.go b/agent/app/dto/agents.go index 8fa96d409507..cea1804dfa73 100644 --- a/agent/app/dto/agents.go +++ b/agent/app/dto/agents.go @@ -590,25 +590,32 @@ type AgentConfigFile struct { type AgentSkillSearchReq struct { AgentID uint `json:"agentId" validate:"required"` - Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub"` + Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub official skills-sh"` Keyword string `json:"keyword" validate:"required"` } type AgentSkillItem struct { - Name string `json:"name"` - Description string `json:"description"` - Source string `json:"source"` - Bundled bool `json:"bundled"` - Disabled bool `json:"disabled"` + Name string `json:"name"` + Description string `json:"description"` + Category string `json:"category"` + Tags []string `json:"tags"` + Source string `json:"source"` + Trust string `json:"trust"` + Identifier string `json:"identifier"` + Bundled bool `json:"bundled"` + Disabled bool `json:"disabled"` + Uninstallable bool `json:"uninstallable"` } type AgentSkillSearchItem struct { Slug string `json:"slug"` + Identifier string `json:"identifier"` Name string `json:"name"` Description string `json:"description"` Summary string `json:"summary"` Version string `json:"version"` Source string `json:"source"` + Trust string `json:"trust"` Score string `json:"score"` } @@ -620,7 +627,12 @@ type AgentSkillUpdateReq struct { type AgentSkillInstallReq struct { AgentID uint `json:"agentId" validate:"required"` - Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub"` + Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub official skills-sh"` Slug string `json:"slug" validate:"required"` TaskID string `json:"taskID" validate:"required"` } + +type AgentSkillUninstallReq struct { + AgentID uint `json:"agentId" validate:"required"` + Name string `json:"name" validate:"required"` +} diff --git a/agent/app/service/agents.go b/agent/app/service/agents.go index faf61f32cc48..eb8e6e64f673 100644 --- a/agent/app/service/agents.go +++ b/agent/app/service/agents.go @@ -53,6 +53,7 @@ type IAgentService interface { SearchSkills(req dto.AgentSkillSearchReq) ([]dto.AgentSkillSearchItem, error) UpdateSkill(req dto.AgentSkillUpdateReq) error InstallSkill(req dto.AgentSkillInstallReq) error + UninstallSkill(req dto.AgentSkillUninstallReq) error CreateRole(req dto.AgentRoleCreateReq) (*dto.AgentRoleCreateResp, error) DeleteRole(req dto.AgentRoleDeleteReq) error @@ -455,9 +456,9 @@ func (a AgentService) GetModelConfig(req dto.AgentIDReq) (*dto.AgentModelConfig, if err != nil { return nil, err } - model := resolveHermesConfiguredModelID(account, accountModels, cfg.Model.Default) - if model == "" { - model = agent.Model + model, err := resolveHermesConfiguredModelIDStrict(account, accountModels, cfg.Model.Default) + if err != nil { + return nil, err } return &dto.AgentModelConfig{ AccountID: agent.AccountID, diff --git a/agent/app/service/agents_hermes.go b/agent/app/service/agents_hermes.go index 652d9f62aad3..21d61c58eada 100644 --- a/agent/app/service/agents_hermes.go +++ b/agent/app/service/agents_hermes.go @@ -10,6 +10,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/model" providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider" + "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/utils/common" agentenv "github.com/1Panel-dev/1Panel/agent/utils/env" @@ -19,6 +20,7 @@ import ( ) const hermesWorkspaceDir = "/opt/data/workspace" +const hermesExecutablePath = "/opt/hermes/.venv/bin/hermes" type hermesConfig struct { Model hermesModelConfig `yaml:"model"` @@ -52,6 +54,17 @@ func buildHermesDockerExecArgs(containerName string, hermesArgs ...string) []str return buildHermesDockerExecCommandArgs(containerName, "hermes", hermesArgs...) } +func buildHermesSkillUninstallArgs(containerName, skillName string) []string { + return buildHermesDockerExecCommandArgs( + containerName, + "sh", + "-lc", + fmt.Sprintf(`printf 'y\n' | %s skills uninstall "$1"`, hermesExecutablePath), + "sh", + skillName, + ) +} + func writeHermesConfig(confDir string, account *model.AgentAccount, modelName string, timezone string) error { if strings.TrimSpace(confDir) == "" { return fmt.Errorf("config dir is required") @@ -386,6 +399,14 @@ func resolveHermesConfiguredModelID(account *model.AgentAccount, accountModels [ return "" } +func resolveHermesConfiguredModelIDStrict(account *model.AgentAccount, accountModels []dto.AgentAccountModel, configuredModel string) (string, error) { + modelID := resolveHermesConfiguredModelID(account, accountModels, configuredModel) + if modelID == "" { + return "", buserr.New("ErrAgentModelNotInAccount") + } + return modelID, nil +} + func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry { if account == nil { return nil diff --git a/agent/app/service/agents_hermes_channels.go b/agent/app/service/agents_hermes_channels.go index deda798084be..5116329727b1 100644 --- a/agent/app/service/agents_hermes_channels.go +++ b/agent/app/service/agents_hermes_channels.go @@ -57,13 +57,15 @@ func readHermesQQBotChannelConfig(confDir string) (*dto.AgentQQBotConfig, error) } groupAllowFrom := extractStringList(extra["group_allow_from"]) - return &dto.AgentQQBotConfig{ + result := &dto.AgentQQBotConfig{ Enabled: extractBoolValue(platform["enabled"], false) && appID != "" && clientSecret != "", DmPolicy: dmPolicy, AllowFrom: allowFrom, GroupPolicy: groupPolicy, GroupAllowFrom: groupAllowFrom, - Bots: []dto.AgentQQBotBot{ + } + if appID != "" || clientSecret != "" { + result.Bots = []dto.AgentQQBotBot{ { AgentChannelBotBase: dto.AgentChannelBotBase{ AccountID: "default", @@ -74,8 +76,9 @@ func readHermesQQBotChannelConfig(confDir string) (*dto.AgentQQBotConfig, error) AppID: appID, ClientSecret: clientSecret, }, - }, - }, nil + } + } + return result, nil } func writeHermesQQBotChannelConfig(confDir string, config dto.AgentQQBotConfig) error { diff --git a/agent/app/service/agents_hermes_skills.go b/agent/app/service/agents_hermes_skills.go new file mode 100644 index 000000000000..a4b03520344b --- /dev/null +++ b/agent/app/service/agents_hermes_skills.go @@ -0,0 +1,215 @@ +package service + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +type hermesSkillsListEntry struct { + Name string `json:"name"` + Description string `json:"description"` + Category string `json:"category"` +} + +type hermesSkillsListPayload struct { + Skills []hermesSkillsListEntry `json:"skills"` +} + +func listHermesSkills(containerName string) ([]dto.AgentSkillItem, error) { + output, err := runHermesSkillsCommandWithStdout(2*time.Minute, containerName, "list", "--source", "all") + if err != nil { + return nil, err + } + items, err := parseHermesSkillsListOutput(output) + if err != nil { + return nil, err + } + metadata, err := readHermesSkillsListMetadata(containerName) + if err != nil { + return nil, err + } + return mergeHermesSkillsWithMetadata(items, metadata), nil +} + +func searchHermesSkills(containerName, source, keyword string) ([]dto.AgentSkillSearchItem, error) { + if source != "official" && source != "skills-sh" { + return nil, fmt.Errorf("unsupported hermes skill source: %s", source) + } + output, err := runHermesSkillsCommandWithStdout( + 2*time.Minute, + containerName, + "search", + keyword, + "--source", + source, + "--limit", + "20", + ) + if err != nil { + return nil, err + } + return parseHermesSkillSearchOutput(output) +} + +func runHermesSkillsCommandWithStdout(timeout time.Duration, containerName string, hermesArgs ...string) (string, error) { + args := []string{"exec", "-e", "COLUMNS=240", "-u", "hermes", containerName, "hermes", "skills"} + args = append(args, hermesArgs...) + return cmd.NewCommandMgr(cmd.WithTimeout(timeout)).RunWithStdout("docker", args...) +} + +func readHermesSkillsListMetadata(containerName string) (map[string]hermesSkillsListEntry, error) { + output, err := cmd.NewCommandMgr(cmd.WithTimeout(2*time.Minute)).RunWithStdout( + "docker", + "exec", + "-u", + "hermes", + containerName, + "python", + "-c", + "import sys; sys.path.insert(0, '/opt/hermes'); from tools.skills_tool import skills_list; print(skills_list())", + ) + if err != nil { + return nil, err + } + return parseHermesSkillsListMetadataOutput(output) +} + +func parseHermesSkillsListOutput(output string) ([]dto.AgentSkillItem, error) { + headers, rows, err := parseHermesTableOutput(output) + if err != nil { + return nil, err + } + items := make([]dto.AgentSkillItem, 0, len(rows)) + for _, row := range rows { + item := dto.AgentSkillItem{ + Name: row[headers["Name"]], + Category: row[headers["Category"]], + Source: row[headers["Source"]], + Trust: row[headers["Trust"]], + Uninstallable: row[headers["Source"]] != "builtin" && row[headers["Source"]] != "local", + } + items = append(items, item) + } + return items, nil +} + +func parseHermesSkillsListMetadataOutput(output string) (map[string]hermesSkillsListEntry, error) { + if strings.TrimSpace(output) == "" { + return map[string]hermesSkillsListEntry{}, nil + } + var payload hermesSkillsListPayload + if err := json.Unmarshal([]byte(output), &payload); err != nil { + return nil, err + } + metadata := make(map[string]hermesSkillsListEntry, len(payload.Skills)) + for _, skill := range payload.Skills { + if skill.Name == "" { + continue + } + metadata[skill.Name] = skill + } + return metadata, nil +} + +func mergeHermesSkillsWithMetadata(items []dto.AgentSkillItem, metadata map[string]hermesSkillsListEntry) []dto.AgentSkillItem { + for i := range items { + entry, ok := metadata[items[i].Name] + if !ok { + continue + } + items[i].Description = entry.Description + if items[i].Category == "" && entry.Category != "" { + items[i].Category = entry.Category + } + } + return items +} + +func parseHermesSkillSearchOutput(output string) ([]dto.AgentSkillSearchItem, error) { + if strings.Contains(output, "No skills found matching your query.") { + return []dto.AgentSkillSearchItem{}, nil + } + headers, rows, err := parseHermesTableOutput(output) + if err != nil { + return nil, err + } + items := make([]dto.AgentSkillSearchItem, 0, len(rows)) + for _, row := range rows { + identifier := row[headers["Identifier"]] + items = append(items, dto.AgentSkillSearchItem{ + Slug: identifier, + Identifier: identifier, + Name: row[headers["Name"]], + Description: row[headers["Description"]], + Source: row[headers["Source"]], + Trust: row[headers["Trust"]], + }) + } + return items, nil +} + +func parseHermesTableOutput(output string) (map[string]int, [][]string, error) { + lines := strings.Split(strings.TrimSpace(ansiEscapePattern.ReplaceAllString(output, "")), "\n") + var headers []string + rows := make([][]string, 0) + var current []string + + for _, rawLine := range lines { + line := strings.TrimSpace(rawLine) + if (!strings.HasPrefix(line, "│") || !strings.HasSuffix(line, "│")) && + (!strings.HasPrefix(line, "┃") || !strings.HasSuffix(line, "┃")) { + continue + } + line = strings.ReplaceAll(line, "┃", "│") + parts := strings.Split(line, "│") + if len(parts) < 3 { + continue + } + cols := make([]string, 0, len(parts)-2) + for _, part := range parts[1 : len(parts)-1] { + cols = append(cols, strings.TrimSpace(part)) + } + if len(headers) == 0 { + headers = cols + continue + } + if cols[0] != "" { + if current != nil { + rows = append(rows, current) + } + current = cols + continue + } + if current == nil { + continue + } + for i := range cols { + if cols[i] == "" { + continue + } + if current[i] == "" { + current[i] = cols[i] + continue + } + current[i] = current[i] + " " + cols[i] + } + } + + if current != nil { + rows = append(rows, current) + } + if len(headers) == 0 { + return nil, nil, fmt.Errorf("hermes skills table not found") + } + + headerIndex := make(map[string]int, len(headers)) + for i, header := range headers { + headerIndex[header] = i + } + return headerIndex, rows, nil +} diff --git a/agent/app/service/agents_skills.go b/agent/app/service/agents_skills.go index bc0c12f92167..defb55214ab8 100644 --- a/agent/app/service/agents_skills.go +++ b/agent/app/service/agents_skills.go @@ -9,6 +9,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/cmd" ) @@ -41,13 +42,19 @@ var clawhubSearchLinePattern = regexp.MustCompile(`^(\S+)\s+(.+?)\s+\(([\d.]+)\) var ansiEscapePattern = regexp.MustCompile(`\x1b\[[0-9;?]*[ -/]*[@-~]`) func (a AgentService) ListSkills(req dto.AgentIDReq) ([]dto.AgentSkillItem, error) { - _, install, err := a.loadOpenclawAgentAndInstall(req.AgentID) + agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return nil, err } if err := ensureContainerRunning(install.ContainerName); err != nil { return nil, err } + if agent.AgentType == constant.AppHermesAgent { + return listHermesSkills(install.ContainerName) + } + if agent.AgentType != constant.AppOpenclaw { + return nil, fmt.Errorf("%s does not support", agent.AgentType) + } output, err := runDockerExecWithStdout(5*time.Minute, install.ContainerName, "sh", "-c", "openclaw skills list --json 2>&1") if err != nil { return nil, err @@ -59,13 +66,19 @@ func (a AgentService) ListSkills(req dto.AgentIDReq) ([]dto.AgentSkillItem, erro } func (a AgentService) SearchSkills(req dto.AgentSkillSearchReq) ([]dto.AgentSkillSearchItem, error) { - _, install, err := a.loadOpenclawAgentAndInstall(req.AgentID) + agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return nil, err } if err := ensureContainerRunning(install.ContainerName); err != nil { return nil, err } + if agent.AgentType == constant.AppHermesAgent { + return searchHermesSkills(install.ContainerName, req.Source, req.Keyword) + } + if agent.AgentType != constant.AppOpenclaw { + return nil, fmt.Errorf("%s does not support", agent.AgentType) + } output, err := loadOpenclawSkillSearchOutput(install.ContainerName, req.Source, req.Keyword) if err != nil { return nil, err @@ -102,7 +115,7 @@ func (a AgentService) UpdateSkill(req dto.AgentSkillUpdateReq) error { } func (a AgentService) InstallSkill(req dto.AgentSkillInstallReq) error { - _, install, err := a.loadOpenclawAgentAndInstall(req.AgentID) + agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err } @@ -113,6 +126,21 @@ func (a AgentService) InstallSkill(req dto.AgentSkillInstallReq) error { if err != nil { return err } + if agent.AgentType == constant.AppHermesAgent { + installTask.AddSubTask("Install Hermes skill", func(t *task.Task) error { + mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(20*time.Minute)) + return mgr.Run("docker", buildHermesDockerExecArgs(install.ContainerName, "skills", "install", req.Slug, "--yes")...) + }, nil) + go func() { + if err := installTask.Execute(); err != nil { + global.LOG.Errorf("install hermes skill failed: %v", err) + } + }() + return nil + } + if agent.AgentType != constant.AppOpenclaw { + return fmt.Errorf("%s does not support", agent.AgentType) + } installTask.AddSubTask("Install OpenClaw skill", func(t *task.Task) error { mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(20*time.Minute)) return mgr.Run("docker", "exec", install.ContainerName, "sh", "-c", buildOpenclawSkillInstallCommand(req.Source, req.Slug)) @@ -125,6 +153,23 @@ func (a AgentService) InstallSkill(req dto.AgentSkillInstallReq) error { return nil } +func (a AgentService) UninstallSkill(req dto.AgentSkillUninstallReq) error { + agent, install, err := a.loadAgentAndInstall(req.AgentID) + if err != nil { + return err + } + if agent.AgentType != constant.AppHermesAgent { + return fmt.Errorf("%s does not support", agent.AgentType) + } + if err := ensureContainerRunning(install.ContainerName); err != nil { + return err + } + return cmd.NewCommandMgr(cmd.WithTimeout(5*time.Minute)).Run( + "docker", + buildHermesSkillUninstallArgs(install.ContainerName, req.Name)..., + ) +} + func parseOpenclawSkillsList(output string) ([]dto.AgentSkillItem, error) { payloadBytes, err := extractEmbeddedJSON(output) if err != nil { diff --git a/agent/router/ro_ai.go b/agent/router/ro_ai.go index 4f412d569547..4776623ca91b 100644 --- a/agent/router/ro_ai.go +++ b/agent/router/ro_ai.go @@ -98,6 +98,7 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) { aiToolsRouter.POST("/agents/skills/search", baseApi.SearchAgentSkills) aiToolsRouter.POST("/agents/skills/update", baseApi.UpdateAgentSkill) aiToolsRouter.POST("/agents/skills/install", baseApi.InstallAgentSkill) + aiToolsRouter.POST("/agents/skills/uninstall", baseApi.UninstallAgentSkill) aiToolsRouter.POST("/agents/channel/pairing/approve", baseApi.ApproveAgentChannelPairing) } } diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts index a449eab0e88e..1bc8128ebe85 100644 --- a/frontend/src/api/interface/ai.ts +++ b/frontend/src/api/interface/ai.ts @@ -621,7 +621,7 @@ export namespace AI { export interface AgentChannelPairingApproveReq { agentId: number; - type: 'feishu' | 'telegram' | 'discord' | 'wecom'; + type: 'feishu' | 'telegram' | 'discord' | 'wecom' | 'qqbot'; pairingCode: string; accountId?: string; } @@ -845,25 +845,32 @@ export namespace AI { export interface AgentSkillSearchReq { agentId: number; - source: 'clawhub-global' | 'clawhub-cn' | 'skillhub'; + source: 'clawhub-global' | 'clawhub-cn' | 'skillhub' | 'official' | 'skills-sh'; keyword: string; } export interface AgentSkillItem { name: string; description: string; + category: string; + tags: string[]; source: string; + trust: string; + identifier: string; bundled: boolean; disabled: boolean; + uninstallable: boolean; } export interface AgentSkillSearchItem { slug: string; + identifier: string; name: string; description: string; summary: string; version: string; source: string; + trust: string; score: string; } @@ -875,8 +882,13 @@ export namespace AI { export interface AgentSkillInstallReq { agentId: number; - source: 'clawhub-global' | 'clawhub-cn' | 'skillhub'; + source: 'clawhub-global' | 'clawhub-cn' | 'skillhub' | 'official' | 'skills-sh'; slug: string; taskID: string; } + + export interface AgentSkillUninstallReq { + agentId: number; + name: string; + } } diff --git a/frontend/src/api/modules/ai.ts b/frontend/src/api/modules/ai.ts index 4b77be3bd909..d96f627cf525 100644 --- a/frontend/src/api/modules/ai.ts +++ b/frontend/src/api/modules/ai.ts @@ -326,6 +326,10 @@ export const installAgentSkill = (req: AI.AgentSkillInstallReq) => { return http.post(`/ai/agents/skills/install`, req); }; +export const uninstallAgentSkill = (req: AI.AgentSkillUninstallReq) => { + return http.post(`/ai/agents/skills/uninstall`, req); +}; + export const approveAgentChannelPairing = (req: AI.AgentChannelPairingApproveReq) => { return http.post(`/ai/agents/channel/pairing/approve`, req, TimeoutEnum.T_5M); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 7187bcdf4e53..bcbb02b8e4bc 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -719,6 +719,7 @@ const message = { skillsMarketSourceClawhubChina: 'ClawHub (China)', skillsMarketSourceSkillhub: 'SkillHub (Tencent)', skillsScore: 'Score', + skillsMarketSourceOfficial: 'Official', versionUnsupportedTitle: 'This feature is not supported in the current version', versionUnsupportedHelper: 'Please upgrade OpenClaw to version {0} or later.', skillsStatusDisabled: 'Disabled', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index fa06a484d70f..13b746c96f20 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -726,6 +726,7 @@ const message = { skillsMarketSourceClawhubChina: 'ClawHub (China)', skillsMarketSourceSkillhub: 'SkillHub (Tencent)', skillsScore: 'Puntuación', + skillsMarketSourceOfficial: 'Oficial', versionUnsupportedTitle: 'Esta función no es compatible con la versión actual', versionUnsupportedHelper: 'Actualice OpenClaw a la versión {0} o posterior.', skillsStatusDisabled: 'Deshabilitado', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index fbbb67ecd960..1f62d50397cd 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -720,6 +720,7 @@ const message = { skillsMarketSourceClawhubChina: 'ClawHub(中国)', skillsMarketSourceSkillhub: 'SkillHub(Tencent)', skillsScore: 'スコア', + skillsMarketSourceOfficial: '公式', versionUnsupportedTitle: '現在のバージョンではこの機能はサポートされていません', versionUnsupportedHelper: 'OpenClaw をバージョン {0} 以降にアップグレードしてください。', skillsStatusDisabled: '無効', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 272ff3e42010..afc483edf06e 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -712,6 +712,7 @@ const message = { skillsMarketSourceClawhubChina: 'ClawHub (중국)', skillsMarketSourceSkillhub: 'SkillHub (Tencent)', skillsScore: '점수', + skillsMarketSourceOfficial: '공식', versionUnsupportedTitle: '현재 버전에서는 이 기능을 지원하지 않습니다', versionUnsupportedHelper: 'OpenClaw 를 버전 {0} 이상으로 업그레이드하세요.', skillsStatusDisabled: '비활성화됨', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index c56d471d5d21..2c924e1f8f66 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -727,6 +727,7 @@ const message = { skillsMarketSourceClawhubChina: 'ClawHub (China)', skillsMarketSourceSkillhub: 'SkillHub (Tencent)', skillsScore: 'Skor', + skillsMarketSourceOfficial: 'Rasmi', versionUnsupportedTitle: 'Ciri ini tidak disokong dalam versi semasa', versionUnsupportedHelper: 'Sila naik taraf OpenClaw ke versi {0} atau lebih baharu.', skillsStatusDisabled: 'Dilumpuhkan', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 4c0419df98e4..4ae1e36a4759 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -721,6 +721,7 @@ const message = { skillsMarketSourceClawhubChina: 'ClawHub (China)', skillsMarketSourceSkillhub: 'SkillHub (Tencent)', skillsScore: 'Pontuação', + skillsMarketSourceOfficial: 'Oficial', versionUnsupportedTitle: 'Este recurso não é compatível com a versão atual', versionUnsupportedHelper: 'Atualize o OpenClaw para a versão {0} ou superior.', skillsStatusDisabled: 'Desativado', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 63310033c16a..dfa9137961f3 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -719,6 +719,7 @@ const message = { skillsMarketSourceClawhubChina: 'ClawHub (Китай)', skillsMarketSourceSkillhub: 'SkillHub (Tencent)', skillsScore: 'Оценка', + skillsMarketSourceOfficial: 'Официальный', versionUnsupportedTitle: 'Эта функция не поддерживается в текущей версии', versionUnsupportedHelper: 'Пожалуйста, обновите OpenClaw до версии {0} или выше.', skillsStatusDisabled: 'Отключено', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index b05266a3371b..53e3819d3f1c 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -723,6 +723,7 @@ const message = { skillsMarketSourceClawhubChina: 'ClawHub (Çin)', skillsMarketSourceSkillhub: 'SkillHub (Tencent)', skillsScore: 'Puan', + skillsMarketSourceOfficial: 'Resmi', versionUnsupportedTitle: 'Bu özellik mevcut sürümde desteklenmiyor', versionUnsupportedHelper: 'Lütfen OpenClawı {0} veya üzeri bir sürüme yükseltin.', skillsStatusDisabled: 'Devre dışı', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index a010ea69ea06..1bae6c25f536 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -685,6 +685,7 @@ const message = { skillsMarketSourceClawhubChina: 'ClawHub(中國)', skillsMarketSourceSkillhub: 'SkillHub(騰訊)', skillsScore: '評分', + skillsMarketSourceOfficial: '官方', versionUnsupportedTitle: '當前版本暫不支援該功能', versionUnsupportedHelper: '請升級 OpenClaw 到 {0} 或以上版本後使用。', skillsStatusDisabled: '已禁用', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index b3fd2ebcef3b..017f01d0714e 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -684,6 +684,7 @@ const message = { skillsMarketSourceClawhubChina: 'ClawHub(中国)', skillsMarketSourceSkillhub: 'SkillHub(腾讯)', skillsScore: '评分', + skillsMarketSourceOfficial: '官方', versionUnsupportedTitle: '当前版本暂不支持该功能', versionUnsupportedHelper: '请升级 OpenClaw 到 {0} 或以上版本后使用。', skillsStatusDisabled: '已禁用', diff --git a/frontend/src/views/ai/agents/agent/config/index.vue b/frontend/src/views/ai/agents/agent/config/index.vue index e3f2e2223807..d9b24d1e6155 100644 --- a/frontend/src/views/ai/agents/agent/config/index.vue +++ b/frontend/src/views/ai/agents/agent/config/index.vue @@ -15,8 +15,12 @@ - - + + - -
- - {{ t('aiTools.agents.skillsMarket') }} - {{ t('app.installed') }} - - -
-
- - -
- - - - -
- - -
+ + - - diff --git a/frontend/src/views/ai/agents/agent/config/tabs/skills/hermes.vue b/frontend/src/views/ai/agents/agent/config/tabs/skills/hermes.vue new file mode 100644 index 000000000000..125eae361a91 --- /dev/null +++ b/frontend/src/views/ai/agents/agent/config/tabs/skills/hermes.vue @@ -0,0 +1,406 @@ + + + + + diff --git a/frontend/src/views/ai/agents/agent/config/tabs/skills/openclaw.vue b/frontend/src/views/ai/agents/agent/config/tabs/skills/openclaw.vue new file mode 100644 index 000000000000..d435d59ce09d --- /dev/null +++ b/frontend/src/views/ai/agents/agent/config/tabs/skills/openclaw.vue @@ -0,0 +1,468 @@ + + + + + diff --git a/frontend/src/views/ai/agents/agent/index.vue b/frontend/src/views/ai/agents/agent/index.vue index 5de7e2582bd6..26bd246cc2d4 100644 --- a/frontend/src/views/ai/agents/agent/index.vue +++ b/frontend/src/views/ai/agents/agent/index.vue @@ -287,14 +287,14 @@ const buttons = [ click: (row: AI.AgentItem) => openHermesChat(row), show: (row: AI.AgentItem) => row.agentType === 'hermes-agent' && row.status === 'Running', }, - { - label: i18n.global.t('menu.terminal'), - click: (row: AI.AgentItem) => openTerminal(row), - }, { label: i18n.global.t('commons.button.log'), click: (row: AI.AgentItem) => openLog(row), }, + { + label: i18n.global.t('menu.terminal'), + click: (row: AI.AgentItem) => openTerminal(row), + }, { label: i18n.global.t('menu.home'), click: (row: AI.AgentItem) => openOverview(row),