picoclaw/pkg/gateway/agent_api.go
anthrodjear 9f011dbe67 feat(agent): implement agent management and research cockpit
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
2026-05-07 22:50:53 +03:00

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)
}