Why: Squad runs were one file workspace and one JSON file the leader had to emit, so agents, issues, and retries had no durable queue. Evidence: GOWORK=off go test -count=1 -timeout 180s ./... Impact: /v1/runs stays. New routes mount only when the orchestration database opens. SSE for a run id now skips other runs. External CLIs are not a sandbox. Refs: feature/orchestration-platform S1-S7 Co-authored-by: Cursor <cursoragent@cursor.com>
121 lines
3.7 KiB
Go
121 lines
3.7 KiB
Go
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)
|
|
}
|