diff --git a/agent/app/service/agents_overview.go b/agent/app/service/agents_overview.go index a59159b1be8b..610d1fb9d25d 100644 --- a/agent/app/service/agents_overview.go +++ b/agent/app/service/agents_overview.go @@ -103,7 +103,7 @@ func countOpenclawConfiguredChannels(conf map[string]interface{}) int { func loadOpenclawOverviewSkillStats(containerName string) (int, error) { output, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut( - "docker exec %s openclaw gateway call skills.status --json 2>&1", + "docker exec %s openclaw skills list --json 2>&1", 30*time.Second, containerName, ) diff --git a/agent/app/service/agents_skills.go b/agent/app/service/agents_skills.go index b52224510e06..e4c998eec335 100644 --- a/agent/app/service/agents_skills.go +++ b/agent/app/service/agents_skills.go @@ -23,7 +23,10 @@ type openclawSkillListItem struct { Source string `json:"source"` Bundled bool `json:"bundled"` Disabled bool `json:"disabled"` - SkillKey string `json:"skillKey"` +} + +type openclawSkillInfo struct { + SkillKey string `json:"skillKey"` } type skillhubSearchPayload struct { @@ -32,6 +35,7 @@ type skillhubSearchPayload struct { } 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) @@ -41,7 +45,11 @@ func (a AgentService) ListSkills(req dto.AgentIDReq) ([]dto.AgentSkillItem, erro if err := ensureContainerRunning(install.ContainerName); err != nil { return nil, err } - output, err := loadOpenclawSkillsStatus(install.ContainerName) + output, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut( + "docker exec %s openclaw skills list --json 2>&1", + 30*time.Second, + install.ContainerName, + ) if err != nil { return nil, err } @@ -86,7 +94,7 @@ func (a AgentService) UpdateSkill(req dto.AgentSkillUpdateReq) error { if err != nil { return err } - skillKey, err := getOpenclawSkillKeyFromStatus(install.ContainerName, req.Name) + skillKey, err := getOpenclawSkillKey(install.ContainerName, req.Name) if err != nil { return err } @@ -119,12 +127,15 @@ func (a AgentService) InstallSkill(req dto.AgentSkillInstallReq) error { } func parseOpenclawSkillsList(output string) ([]dto.AgentSkillItem, error) { - trimmed := strings.TrimSpace(output) - if trimmed == "" { + payloadBytes, err := extractEmbeddedJSON(output) + if err != nil { + return nil, err + } + if len(payloadBytes) == 0 { return nil, nil } var payload openclawSkillsList - if err := json.Unmarshal([]byte(trimmed), &payload); err != nil { + if err := json.Unmarshal(payloadBytes, &payload); err != nil { return nil, err } items := make([]dto.AgentSkillItem, 0, len(payload.Skills)) @@ -140,14 +151,6 @@ func parseOpenclawSkillsList(output string) ([]dto.AgentSkillItem, error) { return items, nil } -func loadOpenclawSkillsStatus(containerName string) (string, error) { - return cmd.RunDefaultWithStdoutBashCfAndTimeOut( - "docker exec %s openclaw gateway call skills.status --json 2>&1", - 30*time.Second, - containerName, - ) -} - func loadOpenclawSkillSearchOutput(containerName, source, keyword string) (string, error) { switch source { case "skillhub": @@ -229,32 +232,50 @@ func buildOpenclawSkillInstallCommand(source, slug string) string { } } -func getOpenclawSkillKeyFromStatus(containerName, name string) (string, error) { - output, err := loadOpenclawSkillsStatus(containerName) +func getOpenclawSkillKey(containerName, name string) (string, error) { + output, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut( + "docker exec %s openclaw skills info %q --json 2>&1", + 30*time.Second, + containerName, + name, + ) if err != nil { return "", err } - return parseOpenclawSkillKeyFromStatus(name, output) + return parseOpenclawSkillKey(name, output) } -func parseOpenclawSkillKeyFromStatus(name, output string) (string, error) { - trimmed := strings.TrimSpace(output) - if trimmed == "" { - return "", fmt.Errorf("skill %s does not have a skillKey", name) +func parseOpenclawSkillKey(name, output string) (string, error) { + payloadBytes, err := extractEmbeddedJSON(output) + if err != nil { + return "", err } - var payload openclawSkillsList - if err := json.Unmarshal([]byte(trimmed), &payload); err != nil { + var payload openclawSkillInfo + if err := json.Unmarshal(payloadBytes, &payload); err != nil { return "", err } - for _, item := range payload.Skills { - if item.Name == name { - if item.SkillKey == "" { - return "", fmt.Errorf("skill %s does not have a skillKey", name) - } - return item.SkillKey, nil + if payload.SkillKey == "" { + return "", fmt.Errorf("skill %s does not have a skillKey", name) + } + return payload.SkillKey, nil +} + +func extractEmbeddedJSON(output string) ([]byte, error) { + trimmed := strings.TrimSpace(ansiEscapePattern.ReplaceAllString(output, "")) + if trimmed == "" { + return nil, nil + } + for i := 0; i < len(trimmed); i++ { + if trimmed[i] != '{' && trimmed[i] != '[' { + continue + } + decoder := json.NewDecoder(strings.NewReader(trimmed[i:])) + var raw json.RawMessage + if err := decoder.Decode(&raw); err == nil { + return raw, nil } } - return "", fmt.Errorf("skill %s not found", name) + return nil, fmt.Errorf("json payload not found") } func setOpenclawSkillEnabled(conf map[string]interface{}, skillKey string, enabled bool) {