package api import ( "encoding/json" "os" "path/filepath" "sync" "time" "unicode/utf8" "github.com/muyue/muyue/internal/config" ) const shellMaxTokens = 100000 const shellCharsPerToken = 4 const shellSystemPromptBase = `Tu es l'**Analyste Système** de Muyue. Tu es un expert en administration système, DevOps et développement. 1. **AGIS, ne décris pas** — Utilise l'outil terminal pour exécuter, ne te contente pas de proposer des commandes. 2. **SOIS AUTONOME** — Cherche les infos manquantes via des commandes avant de demander à l'utilisateur. Essaie plusieurs approches avant de bloquer. 3. **SOIS CONCIS** — Max 4 lignes par défaut. Pas de préambule. Réponse directe et technique. 4. **GÈRE LES ERREURS** — Si une commande échoue, lis l'erreur, comprends la cause, essaie une approche alternative. 2-3 tentatives avant de rapporter. 5. **SÉCURITÉ** — Ne révèle jamais de credentials. Demande confirmation avant les commandes destructrices (rm -rf, format, etc.). 6. **LANGUE** — Réponds dans la même langue que l'utilisateur. Outil disponible : **terminal** — Exécute des commandes shell sur le système local. Stratégies : - **Diagnostique** — Enchaîne les commandes de diagnostic (ps, df, free, top, journalctl, dmesg, netstat, ss, etc.) - **Parallélisme** — Combine les commandes avec && ou ; quand elles sont indépendantes - **Filtrage** — Utilise grep, awk, sort, head pour extraire l'essentiel des sorties volumineuses - **Non-interactif** — Préfère les commandes non-interactives (apt install -y, non pas apt install) - **Troncature** — Si le résultat dépasse 2000 caractères, résume les points clés au lieu de tout afficher - Décide par toi-même : exécute des commandes pour comprendre l'état du système - Ne demande confirmation que pour les actions destructrices - Si tu ne connais pas la commande exacte, exécute la commande avec --help pour la trouver - Si bloqué : documente ce que tu as essayé, pourquoi, et l'action minimale requise - Ne t'arrête jamais pour une tâche complexe — découpe en étapes et exécute-les 1. Lis le message d'erreur complet (stderr + stdout) 2. Identifie la cause racine (permissions, paquet manquant, config, service) 3. Essaie : vérifier le service, vérifier les logs, chercher le paquet, tester la connexion 4. Propose une solution concrète, pas générique - **Commandes** : blocs markdown avec le langage (bash, sh, etc.) - **Résultats** : résume les métriques clés, pas de dump complet - **Erreurs** : cause + solution en 1-2 lignes - **Succès** : confirmation en 1 ligne - **Analyses** : markdown structuré avec sections si nécessaire Tu peux utiliser des diagrammes Mermaid pour visualiser : - Architecture système (graph TD/LR) - Flux réseau (sequenceDiagram) - Processus (flowchart) - Timeline (gantt) Utilise un bloc de code avec le langage mermaid quand ça clarifie l'explication. Pas pour du texte simple. ` type ShellMessage struct { ID string `json:"id"` Role string `json:"role"` Content string `json:"content"` Time string `json:"time"` } type ShellConvStore struct { mu sync.RWMutex path string msgs []ShellMessage } func NewShellConvStore() *ShellConvStore { dir, err := config.ConfigDir() if err != nil { dir = "/tmp/muyue" } path := filepath.Join(dir, "shell_conversation.json") s := &ShellConvStore{path: path} s.load() return s } func (s *ShellConvStore) load() { data, err := os.ReadFile(s.path) if err != nil { s.msgs = []ShellMessage{} return } json.Unmarshal(data, &s.msgs) if s.msgs == nil { s.msgs = []ShellMessage{} } } func (s *ShellConvStore) save() { data, _ := json.MarshalIndent(s.msgs, "", " ") os.MkdirAll(filepath.Dir(s.path), 0755) os.WriteFile(s.path, data, 0600) } func (s *ShellConvStore) Get() []ShellMessage { s.mu.RLock() defer s.mu.RUnlock() out := make([]ShellMessage, len(s.msgs)) copy(out, s.msgs) return out } func (s *ShellConvStore) Add(role, content string) ShellMessage { s.mu.Lock() defer s.mu.Unlock() msg := ShellMessage{ ID: time.Now().Format("20060102150405.000"), Role: role, Content: content, Time: time.Now().Format(time.RFC3339), } s.msgs = append(s.msgs, msg) s.save() return msg } func (s *ShellConvStore) Clear() { s.mu.Lock() defer s.mu.Unlock() s.msgs = []ShellMessage{} s.save() } func (s *ShellConvStore) ApproxTokens() int { s.mu.RLock() defer s.mu.RUnlock() total := 0 for _, m := range s.msgs { if m.Role == "system" { continue } total += utf8.RuneCountInString(extractDisplayContent(m.Role, m.Content)) / shellCharsPerToken } total += utf8.RuneCountInString(shellSystemPromptBase) / shellCharsPerToken if analysis := LoadSystemAnalysis(); analysis != "" { total += utf8.RuneCountInString(analysis) / shellCharsPerToken } return total } func (s *ShellConvStore) AtLimit() bool { return s.ApproxTokens() >= shellMaxTokens } func LoadSystemAnalysis() string { dir, err := config.ConfigDir() if err != nil { return "" } data, err := os.ReadFile(filepath.Join(dir, "system_analysis.md")) if err != nil { return "" } return string(data) } func SaveSystemAnalysis(content string) error { dir, err := config.ConfigDir() if err != nil { return err } os.MkdirAll(dir, 0755) return os.WriteFile(filepath.Join(dir, "system_analysis.md"), []byte(content), 0644) }