All checks were successful
Stable Release / stable (push) Successful in 1m34s
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>
134 lines
2.5 KiB
Go
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
|
|
}
|