Introduces a comprehensive agent management system and a new research interface within the cockpit. This includes backend API endpoints for CRUD operations on agents, a new agent lifecycle manager, and frontend components for managing agents, skills, and research workflows. - feat(backend): add REST API for agent lifecycle management (list, create, update, delete, import) - feat(backend): implement `pkg/agent/manager` for agent lifecycle control - feat(gateway): register agent API routes in the gateway - feat(frontend): add cockpit tabs for Agents, Skills, and Research - feat(frontend): implement AgentsPage and ResearchPage components - feat(frontend): add `use-agents` and `use-cockpit-skills` hooks - docs: add Agent Management API documentation and update project map - refactor(tools): update ToolSkill metadata handling and regex parsing
169 lines
4.2 KiB
Go
169 lines
4.2 KiB
Go
package gateway
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"os"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/agent/manager"
|
|
"github.com/sipeed/picoclaw/pkg/health"
|
|
)
|
|
|
|
var agentManager *manager.Manager
|
|
|
|
func init() {
|
|
agentManager = manager.NewManager(getWorkspacePath() + "/agents")
|
|
}
|
|
|
|
func getWorkspacePath() string {
|
|
if w := os.Getenv("PICOCLAW_WORKSPACE"); w != "" {
|
|
return w
|
|
}
|
|
home, _ := os.UserHomeDir()
|
|
return home + "/.picoclaw/workspace"
|
|
}
|
|
|
|
// Agent API Handlers
|
|
|
|
func handleAgentsList(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
agents, err := agentManager.ListAgents()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(manager.AgentListResponse{Agents: agents})
|
|
}
|
|
|
|
func handleAgentGet(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
slug := r.URL.Query().Get("slug")
|
|
if slug == "" {
|
|
http.Error(w, "slug parameter required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
agent, err := agentManager.GetAgent(slug)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(agent)
|
|
}
|
|
|
|
func handleAgentCreate(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
var req manager.AgentCreateRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
agent, err := agentManager.CreateAgent(req)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusConflict)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(agent)
|
|
}
|
|
|
|
func handleAgentUpdate(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPut {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
slug := r.URL.Query().Get("slug")
|
|
if slug == "" {
|
|
http.Error(w, "slug parameter required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var req manager.AgentUpdateRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
agent, err := agentManager.UpdateAgent(slug, req)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(agent)
|
|
}
|
|
|
|
func handleAgentDelete(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodDelete {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
slug := r.URL.Query().Get("slug")
|
|
if slug == "" {
|
|
http.Error(w, "slug parameter required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := agentManager.DeleteAgent(slug); err != nil {
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]string{"message": "Agent deleted"})
|
|
}
|
|
|
|
func handleAgentImport(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Content string `json:"content"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
agent, err := agentManager.ImportAgent(req.Content)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusConflict)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(agent)
|
|
}
|
|
|
|
// RegisterAgentAPI registers the agent API routes with the health server.
|
|
func RegisterAgentAPI(s *health.Server) {
|
|
s.HandleFunc("/api/agents", handleAgentsList)
|
|
s.HandleFunc("/api/agent", handleAgentGet)
|
|
s.HandleFunc("/api/agent/create", handleAgentCreate)
|
|
s.HandleFunc("/api/agent/update", handleAgentUpdate)
|
|
s.HandleFunc("/api/agent/delete", handleAgentDelete)
|
|
s.HandleFunc("/api/agent/import", handleAgentImport)
|
|
}
|