All checks were successful
Beta Release / beta (push) Successful in 2m24s
Major changes: - Refactor CLI entry point to Cobra commands (root, setup, scan, doctor, install, update, lsp, mcp, skills, config, version) - Add LSP registry with health checks, auto-install, and editor config generation - Add MCP registry with editor detection, status tracking, and per-editor configuration - Add workflow engine with planner and step execution for automated task chains - Add conversation search, export (Markdown/JSON), and detailed token counting - Add streaming shell chat handler with tool call/result events - Add skill validation, dry-run testing, and export endpoints - Enrich dashboard with Tools/Activity/Status tabs and tool cards grid - Add PRD documentation - Complete i18n for both EN and FR 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
172 lines
4.4 KiB
Go
172 lines
4.4 KiB
Go
package workflow
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/muyue/muyue/internal/config"
|
|
"github.com/muyue/muyue/internal/orchestrator"
|
|
)
|
|
|
|
type Planner struct {
|
|
orchestrator *orchestrator.Orchestrator
|
|
}
|
|
|
|
func NewPlanner(cfg *config.MuyueConfig) (*Planner, error) {
|
|
orb, err := orchestrator.New(cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
orb.SetSystemPrompt(plannerSystemPrompt)
|
|
return &Planner{orchestrator: orb}, nil
|
|
}
|
|
|
|
func (p *Planner) GeneratePlan(ctx context.Context, goal string) ([]Step, error) {
|
|
prompt := buildPlanPrompt(goal)
|
|
|
|
messages := []orchestrator.Message{
|
|
{Role: "user", Content: prompt},
|
|
}
|
|
|
|
resp, err := p.orchestrator.SendWithTools(messages)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(resp.Choices) == 0 || resp.Choices[0].Message.Content == "" {
|
|
return nil, fmt.Errorf("no plan generated")
|
|
}
|
|
|
|
content := resp.Choices[0].Message.Content
|
|
plan, err := parsePlanResponse(content)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return plan, nil
|
|
}
|
|
|
|
func buildPlanPrompt(goal string) string {
|
|
return fmt.Sprintf(`Tu es un planificateur de workflows pour Muyue. L'utilisateur veut accomplir la tâche suivante:
|
|
|
|
"%s"
|
|
|
|
Analyse cette tâche et génère un plan d'exécution en une série d'étapes. Chaque étape est un appel d'outil.
|
|
|
|
Les outils disponibles sont:
|
|
- terminal: Exécuter une commande shell
|
|
- read_file: Lire un fichier
|
|
- list_files: Lister les fichiers d'un répertoire
|
|
- search_files: Rechercher des fichiers par pattern
|
|
- grep_content: Rechercher du texte dans des fichiers
|
|
- get_config: Lire la configuration Muyue
|
|
- set_provider: Configurer un provider AI
|
|
- manage_ssh: Gérer les connexions SSH
|
|
- web_fetch: Récupérer le contenu d'une URL
|
|
|
|
Réponds UNIQUEMENT avec un JSON valide représentant un tableau d'étapes, sans texte avant ou après:
|
|
|
|
[
|
|
{"name": "Nom de l'étape", "tool": "terminal", "args": {"command": "ls -la"}},
|
|
{"name": "Lire le fichier config", "tool": "read_file", "args": {"path": "~/.muyue/config.json"}}
|
|
]
|
|
|
|
Règles:
|
|
- Chaque étape doit avoir: name, tool, args
|
|
- Les args varient selon le tool (voir les définitions)
|
|
- Sois précis dans les commandes
|
|
- Sépare en étapes logiques
|
|
- Ne génère pas plus de 10 étapes`, goal)
|
|
}
|
|
|
|
func parsePlanResponse(content string) ([]Step, error) {
|
|
content = strings.TrimSpace(content)
|
|
|
|
var jsonStr string
|
|
if strings.HasPrefix(content, "```json") {
|
|
lines := strings.Split(content, "\n")
|
|
var jsonLines []string
|
|
for _, line := range lines[1:] {
|
|
if strings.HasPrefix(line, "```") {
|
|
break
|
|
}
|
|
jsonLines = append(jsonLines, line)
|
|
}
|
|
jsonStr = strings.Join(jsonLines, "\n")
|
|
} else if strings.HasPrefix(content, "```") {
|
|
lines := strings.Split(content, "\n")
|
|
var jsonLines []string
|
|
for _, line := range lines[1:] {
|
|
if strings.HasPrefix(line, "```") {
|
|
break
|
|
}
|
|
jsonLines = append(jsonLines, line)
|
|
}
|
|
jsonStr = strings.Join(jsonLines, "\n")
|
|
} else {
|
|
jsonStr = content
|
|
}
|
|
|
|
var rawSteps []map[string]interface{}
|
|
if err := json.Unmarshal([]byte(jsonStr), &rawSteps); err != nil {
|
|
return nil, fmt.Errorf("failed to parse plan JSON: %v\nContent: %s", err, content)
|
|
}
|
|
|
|
steps := make([]Step, 0, len(rawSteps))
|
|
for i, raw := range rawSteps {
|
|
step := Step{
|
|
ID: fmt.Sprintf("step-%d", i),
|
|
Status: StatusPending,
|
|
}
|
|
|
|
if name, ok := raw["name"].(string); ok {
|
|
step.Name = name
|
|
} else {
|
|
step.Name = fmt.Sprintf("Step %d", i+1)
|
|
}
|
|
|
|
if tool, ok := raw["tool"].(string); ok {
|
|
step.Tool = tool
|
|
step.Type = TypeToolCall
|
|
}
|
|
|
|
if args, ok := raw["args"].(map[string]interface{}); ok {
|
|
argsJSON, err := json.Marshal(args)
|
|
if err == nil {
|
|
step.Args = argsJSON
|
|
}
|
|
}
|
|
|
|
if tool, ok := raw["type"].(string); ok {
|
|
switch tool {
|
|
case "approval":
|
|
step.Type = TypeApproval
|
|
case "condition":
|
|
step.Type = TypeCondition
|
|
if cond, ok := raw["condition"].(string); ok {
|
|
step.Condition = cond
|
|
}
|
|
default:
|
|
step.Type = TypeToolCall
|
|
}
|
|
}
|
|
|
|
steps = append(steps, step)
|
|
}
|
|
|
|
return steps, nil
|
|
}
|
|
|
|
const plannerSystemPrompt = `Tu es un assistant de planification de workflows pour Muyue. Tu génères des plans d'exécution sous forme de JSON. Chaque plan est une séquence d'étapes (steps) représentant des appels d'outils.
|
|
|
|
Pour générer un plan:
|
|
1. Comprends l'objectif de l'utilisateur
|
|
2. Identifie les outils nécessaires
|
|
3. Décompose en étapes logiques
|
|
4. Spécifie les paramètres de chaque outil
|
|
|
|
Réponds toujours en JSON valide, sans texte additionnel.`
|
|
|
|
var _ = plannerSystemPrompt |