mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-13 11:40:53 +08:00
Comments, doc comments, UI labels, log and error messages, LLM prompt strings, markdown docs, test data and assertions. Identifiers, enum keys, routes, placeholders and code structure are unchanged. Notes: - docs/漏洞流量证据.md renamed to docs/vulnerability-traffic-evidence.md - tests that exercised CJK specific handling (GBK zip names, Chinese full text search) now use ASCII equivalents; go build passes and the test suite matches the pre translation baseline (remaining failures need a PostgreSQL DSN and fail identically on the original) - frontend mention syntax now uses English kinds (finding, asset, ...) matching the backend, chat mentions tests pass
173 lines
5.4 KiB
Go
173 lines
5.4 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/Autumn-27/artex/db"
|
|
)
|
|
|
|
// Human CRUD endpoints for the overview "Goal Management" card. They write the same goal nodes
|
|
// as the agent-side set_goals tool, but the entry point here is a human editing in the UI;
|
|
// add/edit reuse the "revive task" logic (admitTask resume: terminal→running, lift the pause,
|
|
// queue when needed), delete does not revive (product decision). Each handler uses beginTaskOperation/decInflight to avoid racing task deletion (matching intent CRUD).
|
|
|
|
// listGoals returns all goals of this task (text/vulnclass/state split out) for the goal-management card to render.
|
|
func (s *Server) listGoals(w http.ResponseWriter, r *http.Request) {
|
|
t, ok := s.m.Task(r.PathValue("id"))
|
|
if !ok {
|
|
writeErr(w, 404, "task not found")
|
|
return
|
|
}
|
|
goals, err := t.Store.ListByKind(db.KindGoal, 10000)
|
|
if err != nil {
|
|
writeErr(w, 500, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, 200, map[string]any{"goals": goalDTOs(goals)})
|
|
}
|
|
|
|
// addGoal manually adds one goal: persist it (under the task root's spawns edge) → record
|
|
// a "goal added" trigger to wake the planner → revive the task so it re-judges completion against the new goal.
|
|
func (s *Server) addGoal(w http.ResponseWriter, r *http.Request) {
|
|
t, ok := s.m.Task(r.PathValue("id"))
|
|
if !ok {
|
|
writeErr(w, 404, "task not found")
|
|
return
|
|
}
|
|
if !s.engine.beginTaskOperation(t.ID) {
|
|
writeErr(w, 409, "Task is being deleted; cannot add goal")
|
|
return
|
|
}
|
|
defer s.engine.decInflight(t.ID)
|
|
|
|
var body struct {
|
|
Text string `json:"text"`
|
|
VulnClass string `json:"vulnclass"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
writeErr(w, 400, "invalid JSON")
|
|
return
|
|
}
|
|
text := strings.TrimSpace(body.Text)
|
|
if text == "" {
|
|
writeErr(w, 400, "Goal text cannot be empty")
|
|
return
|
|
}
|
|
payload := map[string]any{"text": text}
|
|
if vc := strings.TrimSpace(body.VulnClass); vc != "" {
|
|
payload["vulnclass"] = vc
|
|
}
|
|
id, err := t.Store.AddGoal(payload, "human")
|
|
if err != nil {
|
|
writeErr(w, 500, err.Error())
|
|
return
|
|
}
|
|
if of, _ := t.Store.OriginFactID(); of > 0 && id > 0 {
|
|
_ = t.Store.Link(of, db.RelSpawns, id) // goal descends from the task root (origin fact)
|
|
}
|
|
t.NotifyGoal([]string{text}) // record a "human added a goal: …" trigger and wake the planner
|
|
s.reviveTask(t) // pull a completed/paused task back to running
|
|
node, _ := t.Store.GetNode(id)
|
|
if node == nil {
|
|
writeErr(w, 500, "failed to read the goal back after writing")
|
|
return
|
|
}
|
|
writeJSON(w, 200, goalDTO(node))
|
|
}
|
|
|
|
// editGoal manually edits a goal's text (and vulnclass): update the store → record a "user
|
|
// changed the goal from old to new" trigger to wake the planner → revive the task so it adjusts direction.
|
|
func (s *Server) editGoal(w http.ResponseWriter, r *http.Request) {
|
|
t, ok := s.m.Task(r.PathValue("id"))
|
|
if !ok {
|
|
writeErr(w, 404, "task not found")
|
|
return
|
|
}
|
|
if !s.engine.beginTaskOperation(t.ID) {
|
|
writeErr(w, 409, "Task is being deleted; cannot edit goal")
|
|
return
|
|
}
|
|
defer s.engine.decInflight(t.ID)
|
|
|
|
gid, err := strconv.ParseInt(r.PathValue("gid"), 10, 64)
|
|
if err != nil || gid <= 0 {
|
|
writeErr(w, 400, "bad goal id")
|
|
return
|
|
}
|
|
var body struct {
|
|
Text string `json:"text"`
|
|
VulnClass string `json:"vulnclass"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
writeErr(w, 400, "invalid JSON")
|
|
return
|
|
}
|
|
text := strings.TrimSpace(body.Text)
|
|
if text == "" {
|
|
writeErr(w, 400, "Goal text cannot be empty")
|
|
return
|
|
}
|
|
node, err := t.Store.GetNode(gid)
|
|
if err != nil {
|
|
writeErr(w, 500, err.Error())
|
|
return
|
|
}
|
|
if node == nil || node.Kind != db.KindGoal {
|
|
writeErr(w, 404, "goal not found")
|
|
return
|
|
}
|
|
oldText := goalDTO(node).Text
|
|
if err := t.Store.UpdateGoalPayload(gid, text, strings.TrimSpace(body.VulnClass)); err != nil {
|
|
writeErr(w, 500, err.Error())
|
|
return
|
|
}
|
|
t.NotifyGoalEdited(oldText, text) // record a "human changed the goal from old to new" trigger and wake the planner
|
|
s.reviveTask(t) // same as add: revive the task to re-judge the new goal
|
|
updated, _ := t.Store.GetNode(gid)
|
|
if updated == nil {
|
|
writeErr(w, 500, "failed to read the goal back after update")
|
|
return
|
|
}
|
|
writeJSON(w, 200, goalDTO(updated))
|
|
}
|
|
|
|
// deleteGoal manually deletes one goal (hard delete, cascades edges/anchors): delete from
|
|
// the store → record a "user deleted goal X" trigger to wake the planner to re-judge the remaining goals. Per product decision, deleting does [not] revive the task.
|
|
func (s *Server) deleteGoal(w http.ResponseWriter, r *http.Request) {
|
|
t, ok := s.m.Task(r.PathValue("id"))
|
|
if !ok {
|
|
writeErr(w, 404, "task not found")
|
|
return
|
|
}
|
|
if !s.engine.beginTaskOperation(t.ID) {
|
|
writeErr(w, 409, "Task is being deleted; cannot delete goal")
|
|
return
|
|
}
|
|
defer s.engine.decInflight(t.ID)
|
|
|
|
gid, err := strconv.ParseInt(r.PathValue("gid"), 10, 64)
|
|
if err != nil || gid <= 0 {
|
|
writeErr(w, 400, "bad goal id")
|
|
return
|
|
}
|
|
node, err := t.Store.GetNode(gid)
|
|
if err != nil {
|
|
writeErr(w, 500, err.Error())
|
|
return
|
|
}
|
|
if node == nil || node.Kind != db.KindGoal {
|
|
writeErr(w, 404, "goal not found")
|
|
return
|
|
}
|
|
text := goalDTO(node).Text
|
|
if err := t.Store.DeleteGoal(gid); err != nil {
|
|
writeErr(w, 500, err.Error())
|
|
return
|
|
}
|
|
t.NotifyGoalDeleted(text) // record a "human deleted the goal: …" trigger and wake the planner (task not revived)
|
|
writeJSON(w, 200, map[string]bool{"ok": true})
|
|
}
|