Files
MuyueWorkspace/internal/api/agent_sessions.go
Augustin 4523bbd42c
All checks were successful
Stable Release / stable (push) Successful in 1m34s
feat: RAG, memory, plugins, lessons, file editor, split panes, Markdown rendering, PWA + UI overhaul
Major additions:
- RAG pipeline (indexing, chunking, search) with sidebar upload button
- Memory system with CRUD API
- Plugins and lessons modules
- MCP discovery and MCP server
- Advanced skills (auto-create, conditional, improver)
- Agent browser/image support, delegate, sessions
- File editor with CodeMirror in split panes
- Markdown rendering via react-markdown + KaTeX + highlight.js
- Raw markdown toggle
- PWA manifest + service worker
- Extension UI redesign with new design tokens and studio-style chat
- Pipeline API for chat streaming
- Mobile responsive layout

💘 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
2026-04-27 21:04:11 +02:00

134 lines
2.5 KiB
Go

package api
import (
"fmt"
"os"
"strings"
"sync"
"time"
)
type AgentSession struct {
ID string `json:"id"`
Type string `json:"type"`
PID int `json:"pid"`
Command string `json:"command"`
StartedAt string `json:"started_at"`
Status string `json:"status"`
Output string `json:"output,omitempty"`
Cwd string `json:"cwd,omitempty"`
}
type AgentSessionTracker struct {
mu sync.RWMutex
sessions map[string]*AgentSession
}
func NewAgentSessionTracker() *AgentSessionTracker {
return &AgentSessionTracker{
sessions: make(map[string]*AgentSession),
}
}
func (t *AgentSessionTracker) Discover() []AgentSession {
t.mu.Lock()
defer t.mu.Unlock()
activePIDs := make(map[int]bool)
for _, s := range t.sessions {
activePIDs[s.PID] = true
}
for _, name := range []string{"crush", "claude"} {
pids := findProcessesByName(name)
for _, pid := range pids {
if !activePIDs[pid] {
session := &AgentSession{
ID: fmt.Sprintf("%s-%d-%d", name, pid, time.Now().UnixMilli()),
Type: name,
PID: pid,
Command: getProcessCommand(pid),
StartedAt: time.Now().Format(time.RFC3339),
Status: "running",
}
t.sessions[session.ID] = session
}
}
}
var result []AgentSession
for _, s := range t.sessions {
if s.Status == "running" {
if !isProcessAlive(s.PID) {
s.Status = "completed"
}
}
result = append(result, *s)
}
return result
}
func (t *AgentSessionTracker) Get(id string) *AgentSession {
t.mu.RLock()
defer t.mu.RUnlock()
s, ok := t.sessions[id]
if !ok {
return nil
}
snapshot := *s
return &snapshot
}
func findProcessesByName(name string) []int {
data, err := os.ReadFile("/proc/" + name + "/stat")
_ = data
_ = err
var pids []int
entries, err := os.ReadDir("/proc")
if err != nil {
return pids
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
var pid int
if _, err := fmt.Sscanf(entry.Name(), "%d", &pid); err != nil {
continue
}
if pid <= 0 || pid == os.Getpid() {
continue
}
cmdline, err := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
if err != nil {
continue
}
cmdStr := string(cmdline)
if strings.Contains(cmdStr, name) {
pids = append(pids, pid)
}
}
return pids
}
func getProcessCommand(pid int) string {
out, err := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
if err != nil {
return ""
}
return strings.ReplaceAll(string(out), "\x00", " ")
}
func isProcessAlive(pid int) bool {
_, err := os.Stat(fmt.Sprintf("/proc/%d", pid))
return err == nil
}