mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-13 19:50:54 +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
178 lines
5.2 KiB
Go
178 lines
5.2 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/Autumn-27/artex/db"
|
|
)
|
|
|
|
// Human CRUD endpoints for the overview "Constraint Management" card + injection-scope
|
|
// switch parsing. Operational constraints (allow/deny) share the task_constraints table
|
|
// with the agent-side set_constraints tool; here a human edits them in the UI. Constraints
|
|
// are prompt context only — edits do [not] notify the planner; the next planning round reads the table anyway (product decision). Each handler uses beginTaskOperation/decInflight to avoid racing task deletion (matching goal/intent CRUD).
|
|
|
|
// Settings keys for the injection-scope switches, both on by default (GetBool's second arg = true).
|
|
const (
|
|
settingConstraintsInjectPlanner = "constraints_inject_planner"
|
|
settingConstraintsInjectWorker = "constraints_inject_worker"
|
|
)
|
|
|
|
// constraintInjectPlanner / constraintInjectWorker report whether operational constraints
|
|
// are injected into the given agent's system prompt (default on). Passed to planner/worker as resolvers: read every round, so toggling takes effect immediately.
|
|
func (s *Server) constraintInjectPlanner() bool {
|
|
return s.m.pg.GetBool(settingConstraintsInjectPlanner, true)
|
|
}
|
|
|
|
func (s *Server) constraintInjectWorker() bool {
|
|
return s.m.pg.GetBool(settingConstraintsInjectWorker, true)
|
|
}
|
|
|
|
// listConstraints returns all operational constraints of this task (allow first, then deny).
|
|
func (s *Server) listConstraints(w http.ResponseWriter, r *http.Request) {
|
|
t, ok := s.m.Task(r.PathValue("id"))
|
|
if !ok {
|
|
writeErr(w, 404, "task not found")
|
|
return
|
|
}
|
|
rows, err := t.Store.ListConstraints()
|
|
if err != nil {
|
|
writeErr(w, 500, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, 200, map[string]any{"constraints": constraintDTOs(rows)})
|
|
}
|
|
|
|
// addConstraint manually adds one operational constraint (kind=allow|deny). Does not notify the planner.
|
|
func (s *Server) addConstraint(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 constraint")
|
|
return
|
|
}
|
|
defer s.engine.decInflight(t.ID)
|
|
|
|
var body struct {
|
|
Text string `json:"text"`
|
|
Kind string `json:"kind"`
|
|
}
|
|
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, "Constraint text cannot be empty")
|
|
return
|
|
}
|
|
kind := normalizeConstraintKind(body.Kind)
|
|
if kind == "" {
|
|
writeErr(w, 400, "kind must be allow or deny")
|
|
return
|
|
}
|
|
id, err := t.Store.AddConstraint(kind, text, "human")
|
|
if err != nil {
|
|
writeErr(w, 500, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, 200, ConstraintDTO{ID: strconv.FormatInt(id, 10), Kind: kind, Text: text, Origin: "human"})
|
|
}
|
|
|
|
// editConstraint manually edits one constraint (kind + text). Does not notify the planner.
|
|
func (s *Server) editConstraint(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 constraint")
|
|
return
|
|
}
|
|
defer s.engine.decInflight(t.ID)
|
|
|
|
cid, err := strconv.ParseInt(r.PathValue("cid"), 10, 64)
|
|
if err != nil || cid <= 0 {
|
|
writeErr(w, 400, "bad constraint id")
|
|
return
|
|
}
|
|
var body struct {
|
|
Text string `json:"text"`
|
|
Kind string `json:"kind"`
|
|
}
|
|
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, "Constraint text cannot be empty")
|
|
return
|
|
}
|
|
kind := normalizeConstraintKind(body.Kind)
|
|
if kind == "" {
|
|
writeErr(w, 400, "kind must be allow or deny")
|
|
return
|
|
}
|
|
if err := t.Store.UpdateConstraint(cid, kind, text); err != nil {
|
|
writeErr(w, 500, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, 200, ConstraintDTO{ID: strconv.FormatInt(cid, 10), Kind: kind, Text: text})
|
|
}
|
|
|
|
// deleteConstraint manually deletes one constraint. Does not notify the planner.
|
|
func (s *Server) deleteConstraint(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 constraint")
|
|
return
|
|
}
|
|
defer s.engine.decInflight(t.ID)
|
|
|
|
cid, err := strconv.ParseInt(r.PathValue("cid"), 10, 64)
|
|
if err != nil || cid <= 0 {
|
|
writeErr(w, 400, "bad constraint id")
|
|
return
|
|
}
|
|
if err := t.Store.DeleteConstraint(cid); err != nil {
|
|
writeErr(w, 500, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, 200, map[string]bool{"ok": true})
|
|
}
|
|
|
|
// normalizeConstraintKind lowercases + validates the kind; "" on invalid.
|
|
func normalizeConstraintKind(k string) string {
|
|
k = strings.TrimSpace(strings.ToLower(k))
|
|
if k == "allow" || k == "deny" {
|
|
return k
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// constraintDTOs converts db rows to the frontend shape.
|
|
func constraintDTOs(in []db.Constraint) []ConstraintDTO {
|
|
out := make([]ConstraintDTO, 0, len(in))
|
|
for _, c := range in {
|
|
out = append(out, ConstraintDTO{
|
|
ID: strconv.FormatInt(c.ID, 10),
|
|
Kind: c.Kind,
|
|
Text: c.Text,
|
|
Origin: c.Origin,
|
|
TS: rfc3339(c.CreatedAt),
|
|
})
|
|
}
|
|
return out
|
|
}
|