Files
Agent-System-Backend/internal/server/human_decision.go
T
weichen 451aa49954 Alias the core package consistently
Why: An interrupted import cleanup removed the cmdbus core import while two human-decision files used an explicit alias.

Evidence: go test ./internal/cmdbus ./internal/server passes.

Impact: The backend worktree is buildable and consistently names the core package without behavior changes.

Refs: W4b human decision verifier
2026-10-15 06:01:32 +00:00

99 lines
3.1 KiB
Go

package server
import (
core "agent-system-core"
"errors"
"net/http"
"strconv"
"strings"
"time"
"agent-system-backend/internal/cmdbus"
"agent-system-backend/internal/store"
)
// resolveHumanDecision handles a card issued by HumanDecisionVerifier.
// Ordinary HITL cards return handled=false and keep the existing run path.
func (s *Server) resolveHumanDecision(w http.ResponseWriter, r *http.Request, resp core.HITLResponse) (bool, error) {
rec, err := s.st.HumanDecision(string(resp.CardID))
if errors.Is(err, store.ErrNotFound) {
return false, nil
}
if err != nil {
return true, err
}
decision := resp.ChoiceID
if decision == "" {
switch strings.TrimSpace(resp.FreeText) {
case "批准", "接受", "同意":
pending, perr := s.st.PendingHumanDecisions(s.owner(r), time.Now())
if perr != nil {
return true, perr
}
if len(pending) != 1 || pending[0].ID != rec.ID {
return true, errors.New("human decision text matches more than one pending card")
}
decision = "accept"
default:
return true, errors.New("human decision requires an explicit choice")
}
}
answered, err := s.st.RespondHumanDecision(s.owner(r), rec.ID, decision, time.Now())
if err != nil {
return true, err
}
out := map[string]any{
"resolved": true, "proofId": answered.ProofID, "decision": answered.Decision,
"proposalId": answered.ProposalID, "proposalRevision": answered.ProposalRevision,
}
if s.Bus != nil && answered.CommandID != "" {
follow := s.Bus.Dispatch(r.Context(), cmdbus.Request{
ID: answered.CommandID,
Params: map[string]any{
"projectId": answered.ProjectID, "proposalId": answered.ProposalID,
"proposalRevision": answered.ProposalRevision, "payloadHash": answered.PayloadHash,
"decision": answered.Decision,
},
Entrance: "card", Actor: answered.HumanActor, Owner: s.owner(r),
ConversationID: answered.ConversationID,
HumanGrant: &cmdbus.HumanGrant{
CardID: answered.ID, ProofID: answered.ProofID,
Actor: answered.HumanActor, Decision: answered.Decision,
},
})
out["receipt"] = follow.Receipt
if follow.Payload != nil {
out["payload"] = follow.Payload
}
}
writeJSON(w, http.StatusOK, out)
return true, nil
}
func (s *Server) sessionHumanGrant(actor, commandID string, params map[string]any) (*cmdbus.HumanGrant, bool) {
if commandID != "rdm.structure.decide" || s.st == nil {
return nil, false
}
project, _ := params["projectId"].(string)
proposal, _ := params["proposalId"].(string)
hash, _ := params["payloadHash"].(string)
decision, _ := params["decision"].(string)
revision := 0
switch v := params["proposalRevision"].(type) {
case float64:
revision = int(v)
case int:
revision = v
case string:
revision, _ = strconv.Atoi(v)
}
if project == "" || proposal == "" || hash == "" || decision == "" || revision < 1 {
return nil, false
}
rec, err := s.st.SessionHumanGrant(actor, commandID, project, proposal, hash, decision, revision, time.Now())
if err != nil {
return nil, false
}
return &cmdbus.HumanGrant{CardID: rec.ID, ProofID: rec.ProofID, Actor: rec.HumanActor, Decision: rec.Decision}, true
}