feat: AI task API, token-based context windows, SSH password auth, sudo bypass detection

Replace message-count context windows with token-budget based ones for both
studio and shell. Add /api/ai/task endpoint for background tool
check/install/update. Enhance sudo blocking to catch piped/chained elevation
commands. Add SSH password support via sshpass and connection editing UI.
Remove realTokens persistence in favor of consumption tracking. Bump to 0.4.1.

💘 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
Augustin
2026-04-26 20:06:20 +02:00
parent c9f2932147
commit d98110ce8a
16 changed files with 446 additions and 105 deletions

View File

@@ -4,12 +4,14 @@ import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"runtime"
"strings"
"time"
"unicode/utf8"
"github.com/muyue/muyue/internal/agent"
"github.com/muyue/muyue/internal/orchestrator"
@@ -133,7 +135,6 @@ func (s *Server) handleShellChatStream(w http.ResponseWriter, orb *orchestrator.
storeContent = string(storeJSON)
}
s.shellConvStore.Add("assistant", storeContent)
s.shellConvStore.AddRealTokens(engine.TotalTokens)
s.consumption.Record(engine.ProviderName(), engine.TotalTokens)
@@ -155,7 +156,6 @@ func (s *Server) handleShellChatNonStream(w http.ResponseWriter, orb *orchestrat
}
s.shellConvStore.Add("assistant", finalContent)
s.shellConvStore.AddRealTokens(engine.TotalTokens)
s.consumption.Record(engine.ProviderName(), engine.TotalTokens)
@@ -167,13 +167,45 @@ func (s *Server) handleShellChatNonStream(w http.ResponseWriter, orb *orchestrat
func (s *Server) buildShellContextMessages() []orchestrator.Message {
history := s.shellConvStore.Get()
start := 0
const shellContextWindow = 20
if len(history) > shellContextWindow {
start = len(history) - shellContextWindow
sysTokens := utf8.RuneCountInString(shellSystemPromptBase) / charsPerToken
if analysis := LoadSystemAnalysis(); analysis != "" {
sysTokens += utf8.RuneCountInString(analysis) / charsPerToken
}
sysTokens += 100
toolsTokens := utf8.RuneCountInString(string(s.shellAgentToolsJSON)) / charsPerToken
responseMargin := 4000
overhead := sysTokens + toolsTokens + responseMargin
available := shellMaxTokens - overhead
if available < 1000 {
available = 1000
}
messages := make([]orchestrator.Message, 0, len(history[start:]))
included := 0
tokensUsed := 0
for i := len(history) - 1; i >= 0; i-- {
msgTokens := utf8.RuneCountInString(history[i].Content) / charsPerToken
if msgTokens == 0 {
msgTokens = 1
}
if tokensUsed+msgTokens > available {
break
}
tokensUsed += msgTokens
included++
}
start := len(history) - included
if start < 0 {
start = 0
}
if start > 0 {
log.Printf("[shell] context budget: %d/%d tokens, including %d/%d messages (dropped %d older)", tokensUsed+overhead, shellMaxTokens, included, len(history), start)
}
messages := make([]orchestrator.Message, 0, included)
for _, m := range history[start:] {
content := m.Content