package platform import ( "encoding/json" "io" "net/http" "time" ) func nowTime() time.Time { return time.Now() } // Mount attaches the orchestration routes. The goal-engine /v1/runs routes stay separate. func Mount(mux *http.ServeMux, st *Store) { mux.HandleFunc("POST /v1/workspaces", func(w http.ResponseWriter, r *http.Request) { var body struct{ Name string } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } ws, err := st.CreateWorkspace(body.Name) write(w, ws, err) }) mux.HandleFunc("POST /v1/agents", func(w http.ResponseWriter, r *http.Request) { var a Agent if err := json.NewDecoder(r.Body).Decode(&a); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } created, err := st.CreateAgent(a) write(w, created, err) }) mux.HandleFunc("POST /v1/squads", func(w http.ResponseWriter, r *http.Request) { var body struct { Squad Members []SquadMember } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } created, err := st.CreateSquad(body.Squad, body.Members) write(w, created, err) }) mux.HandleFunc("POST /v1/issues", func(w http.ResponseWriter, r *http.Request) { var issue Issue if err := json.NewDecoder(r.Body).Decode(&issue); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } created, err := st.CreateIssue(issue) write(w, created, err) }) mux.HandleFunc("POST /v1/issues/{id}/assign", func(w http.ResponseWriter, r *http.Request) { var body struct { Kind string ID string Start bool } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } err := st.AssignIssue(r.PathValue("id"), body.Kind, body.ID, body.Start) write(w, map[string]bool{"ok": err == nil}, err) }) mux.HandleFunc("POST /v1/issues/{id}/comments", func(w http.ResponseWriter, r *http.Request) { var c Comment if err := json.NewDecoder(r.Body).Decode(&c); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } c.IssueID = r.PathValue("id") created, err := st.AddComment(c) write(w, created, err) }) mux.HandleFunc("POST /v1/tasks/claim", func(w http.ResponseWriter, r *http.Request) { task, ok, err := st.Claim(r.URL.Query().Get("runtime"), nowTime()) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } write(w, map[string]any{"task": task, "ok": ok}, nil) }) mux.HandleFunc("POST /v1/webhooks/autopilots/{token}", func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(io.LimitReader(r.Body, 256*1024+1)) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } headers := map[string]string{} for _, key := range []string{"Idempotency-Key", "X-GitHub-Delivery", "X-Hub-Signature-256", "X-GitHub-Event"} { if v := r.Header.Get(key); v != "" { headers[key] = v } } event, err := st.AcceptWebhook(r.PathValue("token"), body, headers) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } write(w, map[string]string{"event": event}, nil) }) mux.HandleFunc("GET /v1/events/ws", func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Upgrade") == "" { http.Error(w, "upgrade required", http.StatusUpgradeRequired) return } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte("{\"type\":\"ready\"}\n")) }) } func write(w http.ResponseWriter, v any, err error) { if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(v) }