107 lines
3.0 KiB
Go
107 lines
3.0 KiB
Go
package runmgr
|
||
|
||
import (
|
||
"regexp"
|
||
"strconv"
|
||
"strings"
|
||
"unicode/utf8"
|
||
|
||
core "agent-system-core"
|
||
)
|
||
|
||
type materialSuggestion struct {
|
||
Label string `json:"label"`
|
||
Basis string `json:"basis"`
|
||
}
|
||
|
||
var (
|
||
materialOptionLine = regexp.MustCompile(`^\s*(?:[0-9]+[.、)]|[-*])\s+(.+)$`)
|
||
materialBasisLine = regexp.MustCompile(`^\s+[-*]\s*依据[::]\s*(.+)$`)
|
||
)
|
||
|
||
func validMaterialSuggestion(s materialSuggestion) bool {
|
||
for _, value := range []string{s.Label, s.Basis} {
|
||
if value == "" || strings.ContainsAny(value, "<>\r\n") || strings.Contains(value, "sourceId=") {
|
||
return false
|
||
}
|
||
}
|
||
return utf8.RuneCountInString(s.Label) <= 80 && utf8.RuneCountInString(s.Basis) <= 240
|
||
}
|
||
|
||
// parseMaterialSuggestions accepts only bounded, grounded options from the
|
||
// completed child answer. Raw child prose never becomes a system reminder.
|
||
func parseMaterialSuggestions(text string) []materialSuggestion {
|
||
inSection := false
|
||
var current *materialSuggestion
|
||
var out []materialSuggestion
|
||
finish := func() bool {
|
||
if current == nil {
|
||
return true
|
||
}
|
||
if !validMaterialSuggestion(*current) || len(out) == 4 {
|
||
return false
|
||
}
|
||
out = append(out, *current)
|
||
current = nil
|
||
return true
|
||
}
|
||
for _, raw := range strings.Split(text, "\n") {
|
||
line := strings.TrimSpace(raw)
|
||
if strings.HasPrefix(line, "#") {
|
||
if inSection {
|
||
break
|
||
}
|
||
if strings.Contains(line, "建议后续处理") {
|
||
inSection = true
|
||
}
|
||
continue
|
||
}
|
||
if !inSection || line == "" {
|
||
continue
|
||
}
|
||
if match := materialBasisLine.FindStringSubmatch(raw); match != nil {
|
||
if current == nil || current.Basis != "" {
|
||
return nil
|
||
}
|
||
current.Basis = strings.TrimSpace(match[1])
|
||
continue
|
||
}
|
||
if match := materialOptionLine.FindStringSubmatch(raw); match != nil {
|
||
if !finish() {
|
||
return nil
|
||
}
|
||
label := strings.Trim(strings.TrimSpace(match[1]), "* \t")
|
||
current = &materialSuggestion{Label: label}
|
||
}
|
||
}
|
||
if !inSection || !finish() || len(out) < 3 || len(out) > 4 {
|
||
return nil
|
||
}
|
||
return out
|
||
}
|
||
|
||
func (m *Manager) materialNoticeUsable(owner string, card core.ActivityCard, payload core.AgentResultPayload) bool {
|
||
if payload.Status != core.RunCompleted || m.cards == nil || card.Task == nil {
|
||
return false
|
||
}
|
||
visible, err := m.cards.Get(owner, card.ID)
|
||
if err != nil || visible.Task == nil || visible.Task.ExecutionState != "completed" ||
|
||
visible.Task.OutcomeCompleteness == "none" || strings.TrimSpace(visible.Task.Conclusion) == "" {
|
||
return false
|
||
}
|
||
return m.taskSourcesAvailable(owner, string(card.ProjectID), card.Task)
|
||
}
|
||
|
||
// materialWorkDecision offers verified suggestions as the work card's optional
|
||
// follow-up choice; anything short of a valid set offers no choice.
|
||
func materialWorkDecision(items []materialSuggestion) *core.WorkDecision {
|
||
if len(items) < 3 || len(items) > 4 {
|
||
return nil
|
||
}
|
||
options := make([]core.WorkOption, 0, len(items))
|
||
for i, item := range items {
|
||
options = append(options, core.WorkOption{ID: "s" + strconv.Itoa(i+1), Label: item.Label})
|
||
}
|
||
return &core.WorkDecision{Mode: "single", State: "open", Options: options}
|
||
}
|