agent-exec transport 68889ca8d4b0eec4cfe89d0cc80b6516/5ca3b7798211
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DocumentOrigin struct {
|
||||
ArtifactID ID `json:"artifactId"`
|
||||
ContentHash string `json:"contentHash"`
|
||||
ParserRevision string `json:"parserRevision"`
|
||||
}
|
||||
type TaskPhase struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
ObservedAt time.Time `json:"observedAt,omitempty"`
|
||||
}
|
||||
type TaskSourceRef struct {
|
||||
ID string `json:"id"`
|
||||
ArtifactID string `json:"artifactId"`
|
||||
ContentHash string `json:"contentHash"`
|
||||
ParserRevision string `json:"parserRevision"`
|
||||
BlockIndex int `json:"blockIndex"`
|
||||
Label string `json:"label"`
|
||||
Quote string `json:"quote,omitempty"`
|
||||
Availability string `json:"availability,omitempty"`
|
||||
}
|
||||
type TaskResultRef struct {
|
||||
ArtifactID ID `json:"artifactId,omitempty"`
|
||||
SurfaceID ID `json:"surfaceId,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
ContentHash string `json:"contentHash,omitempty"`
|
||||
Availability string `json:"availability"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
type TaskFinding struct {
|
||||
ID string `json:"id"`
|
||||
Text string `json:"text"`
|
||||
Impact string `json:"impact,omitempty"`
|
||||
Kind string `json:"kind"`
|
||||
Status string `json:"status"`
|
||||
Revision int64 `json:"revision"`
|
||||
Sources []TaskSourceRef `json:"sources"`
|
||||
Assumptions string `json:"assumptions,omitempty"`
|
||||
Corrects string `json:"corrects,omitempty"`
|
||||
}
|
||||
type TaskSnapshot struct {
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
TaskID ID `json:"taskId"`
|
||||
AttemptID ID `json:"attemptId"`
|
||||
CardID ID `json:"cardId"`
|
||||
ProjectID ID `json:"projectId"`
|
||||
ConversationID ID `json:"conversationId"`
|
||||
SourceMessageID ID `json:"sourceMessageId,omitempty"`
|
||||
ParentRunID ID `json:"parentRunId,omitempty"`
|
||||
RunID ID `json:"runId,omitempty"`
|
||||
OwnerInstanceID string `json:"ownerInstanceId"`
|
||||
ExecutionEpoch int64 `json:"executionEpoch,omitempty"`
|
||||
Revision int64 `json:"revision"`
|
||||
SourceSeq int64 `json:"sourceSeq"`
|
||||
StreamScope string `json:"streamScope"`
|
||||
InputRevision string `json:"inputRevision,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Goal string `json:"goal,omitempty"`
|
||||
ExecutionState string `json:"executionState"`
|
||||
OutcomeCompleteness string `json:"outcomeCompleteness"`
|
||||
DeliveryState string `json:"deliveryState"`
|
||||
Phase TaskPhase `json:"phase"`
|
||||
Focus string `json:"focus,omitempty"`
|
||||
Coverage string `json:"coverage,omitempty"`
|
||||
Remaining string `json:"remaining,omitempty"`
|
||||
NextStep string `json:"nextStep,omitempty"`
|
||||
Conclusion string `json:"conclusion,omitempty"`
|
||||
Findings []TaskFinding `json:"findings,omitempty"`
|
||||
Limitations []string `json:"limitations,omitempty"`
|
||||
Inputs []TaskSourceRef `json:"inputs,omitempty"`
|
||||
Actions []UIAction `json:"actions,omitempty"`
|
||||
ResultRefs []TaskResultRef `json:"resultRefs,omitempty"`
|
||||
LastActivityAt time.Time `json:"lastActivityAt,omitempty"`
|
||||
LastMeaningfulUpdateAt time.Time `json:"lastMeaningfulUpdateAt,omitempty"`
|
||||
CheckpointKeys map[string]string `json:"checkpointKeys,omitempty"`
|
||||
CheckpointCount int `json:"checkpointCount,omitempty"`
|
||||
CheckpointSeq int64 `json:"checkpointSeq,omitempty"`
|
||||
}
|
||||
type TaskSourceQuote struct {
|
||||
SourceID string `json:"sourceId"`
|
||||
BlockIndex int `json:"blockIndex"`
|
||||
Quote string `json:"quote"`
|
||||
}
|
||||
type WorkCheckpointCandidate struct {
|
||||
CheckpointID string `json:"checkpointId"`
|
||||
PhaseID string `json:"phaseId"`
|
||||
Focus string `json:"focus,omitempty"`
|
||||
Remaining string `json:"remaining,omitempty"`
|
||||
NextStep string `json:"nextStep,omitempty"`
|
||||
Findings []WorkFindingCandidate `json:"findings"`
|
||||
}
|
||||
type WorkFindingCandidate struct {
|
||||
FindingKey string `json:"findingKey"`
|
||||
Text string `json:"text"`
|
||||
Impact string `json:"impact,omitempty"`
|
||||
Kind string `json:"kind"`
|
||||
Assumptions string `json:"assumptions,omitempty"`
|
||||
Corrects string `json:"corrects,omitempty"`
|
||||
Sources []TaskSourceQuote `json:"sources"`
|
||||
Evidence *WorkFindingEvidence `json:"evidence,omitempty"`
|
||||
}
|
||||
|
||||
// WorkFindingEvidence is a model-supplied candidate, not a persisted proof.
|
||||
// Backend checks the quoted source text and computes any resulting statement.
|
||||
type WorkEvidencePremise struct {
|
||||
Role string `json:"role"`
|
||||
SourceIndex int `json:"sourceIndex"`
|
||||
Value *int `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type WorkFindingEvidence struct {
|
||||
Mode string `json:"mode"`
|
||||
Premises []WorkEvidencePremise `json:"premises"`
|
||||
Result *int `json:"result,omitempty"`
|
||||
}
|
||||
|
||||
func validateWorkFindingEvidence(e *WorkFindingEvidence, sourceCount int) error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
var required []string
|
||||
switch e.Mode {
|
||||
case "direct_relation":
|
||||
required = []string{"direct_relation"}
|
||||
if e.Result != nil {
|
||||
return fmt.Errorf("checkpoint.evidence: direct relation cannot have result")
|
||||
}
|
||||
case "difference":
|
||||
required = []string{"total", "phase"}
|
||||
if e.Result == nil || *e.Result < 0 || *e.Result > 1000000 {
|
||||
return fmt.Errorf("checkpoint.evidence: difference requires bounded result")
|
||||
}
|
||||
case "unresolved_relation":
|
||||
required = []string{"total", "phase", "dependent"}
|
||||
if e.Result != nil {
|
||||
return fmt.Errorf("checkpoint.evidence: unresolved relation cannot have result")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("checkpoint.evidence: unknown mode")
|
||||
}
|
||||
if len(e.Premises) != len(required) {
|
||||
return fmt.Errorf("checkpoint.evidence: wrong premise count")
|
||||
}
|
||||
roles := make(map[string]bool, len(required))
|
||||
for _, premise := range e.Premises {
|
||||
if roles[premise.Role] || premise.SourceIndex < 0 || premise.SourceIndex >= sourceCount {
|
||||
return fmt.Errorf("checkpoint.evidence: duplicate role or invalid source index")
|
||||
}
|
||||
roles[premise.Role] = true
|
||||
if e.Mode == "direct_relation" {
|
||||
if premise.Value != nil {
|
||||
return fmt.Errorf("checkpoint.evidence: direct relation cannot have value")
|
||||
}
|
||||
} else if premise.Value == nil || *premise.Value < 0 || *premise.Value > 1000000 {
|
||||
return fmt.Errorf("checkpoint.evidence: count premise requires bounded value")
|
||||
}
|
||||
}
|
||||
for _, role := range required {
|
||||
if !roles[role] {
|
||||
return fmt.Errorf("checkpoint.evidence: missing premise role")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate enforces wire limits; owner/source authorization belongs to Backend.
|
||||
func (s TaskSnapshot) Validate() error {
|
||||
if s.SchemaVersion != 1 {
|
||||
return fmt.Errorf("task.schema: unsupported")
|
||||
}
|
||||
if s.TaskID == "" || s.AttemptID == "" || s.CardID == "" || s.ProjectID == "" || s.ConversationID == "" || s.OwnerInstanceID == "" {
|
||||
return fmt.Errorf("task.scope: missing identity")
|
||||
}
|
||||
if s.StreamScope != "conversation:"+s.ConversationID {
|
||||
return fmt.Errorf("task.scope: inconsistent stream")
|
||||
}
|
||||
if s.Revision < 1 || s.Revision > 9007199254740991 || s.SourceSeq < 0 || s.SourceSeq > 9007199254740991 || s.ExecutionEpoch < 0 {
|
||||
return fmt.Errorf("task.revision: unsafe cursor")
|
||||
}
|
||||
switch s.ExecutionState {
|
||||
case "queued", "running", "awaiting_user", "completed", "failed", "interrupted":
|
||||
default:
|
||||
return fmt.Errorf("task.state: unknown")
|
||||
}
|
||||
if (s.ExecutionState == "running" || s.ExecutionState == "awaiting_user") && s.RunID == "" {
|
||||
return fmt.Errorf("task.run: active task requires run")
|
||||
}
|
||||
switch s.OutcomeCompleteness {
|
||||
case "none", "partial", "complete":
|
||||
default:
|
||||
return fmt.Errorf("task.outcome: unknown")
|
||||
}
|
||||
switch s.DeliveryState {
|
||||
case "pending", "available", "reported":
|
||||
default:
|
||||
return fmt.Errorf("task.delivery: unknown")
|
||||
}
|
||||
active := 0
|
||||
for _, f := range s.Findings {
|
||||
if f.ID == "" || f.Revision < 1 {
|
||||
return fmt.Errorf("task.finding: missing identity")
|
||||
}
|
||||
switch f.Kind {
|
||||
case "material_record", "analysis", "provisional", "needs_user", "verified":
|
||||
default:
|
||||
return fmt.Errorf("task.finding.kind: unknown")
|
||||
}
|
||||
switch f.Status {
|
||||
case "active":
|
||||
active++
|
||||
case "corrected", "withdrawn", "superseded":
|
||||
default:
|
||||
return fmt.Errorf("task.finding.status: unknown")
|
||||
}
|
||||
}
|
||||
if active > 12 {
|
||||
return fmt.Errorf("task.findings: too many active")
|
||||
}
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(b) > 64*1024 {
|
||||
return fmt.Errorf("task.size: exceeds 64KiB")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s TaskSnapshot) Terminal() bool {
|
||||
return s.ExecutionState == "completed" || s.ExecutionState == "failed" || s.ExecutionState == "interrupted"
|
||||
}
|
||||
func ValidateWorkCheckpoint(c WorkCheckpointCandidate) error {
|
||||
if strings.TrimSpace(c.CheckpointID) == "" || len(c.CheckpointID) > 128 {
|
||||
return fmt.Errorf("checkpoint.id: required or too long")
|
||||
}
|
||||
switch c.PhaseID {
|
||||
case "reading", "crosscheck", "summarizing", "draft-ready":
|
||||
default:
|
||||
return fmt.Errorf("checkpoint.phase: unknown")
|
||||
}
|
||||
if len(c.Findings) == 0 || len(c.Findings) > 6 {
|
||||
return fmt.Errorf("checkpoint.findings: require 1..6")
|
||||
}
|
||||
keys := map[string]bool{}
|
||||
for _, f := range c.Findings {
|
||||
switch f.Kind {
|
||||
case "material_record", "analysis", "provisional", "needs_user":
|
||||
default:
|
||||
return fmt.Errorf("checkpoint.kind: model cannot grant verified status")
|
||||
}
|
||||
if f.FindingKey == "" || keys[f.FindingKey] || strings.TrimSpace(f.Text) == "" {
|
||||
return fmt.Errorf("checkpoint.finding: empty or duplicate key")
|
||||
}
|
||||
keys[f.FindingKey] = true
|
||||
if len(f.Sources) == 0 {
|
||||
return fmt.Errorf("checkpoint.sources: evidence required")
|
||||
}
|
||||
for _, src := range f.Sources {
|
||||
if src.SourceID == "" || src.BlockIndex < 0 || strings.TrimSpace(src.Quote) == "" {
|
||||
return fmt.Errorf("checkpoint.source: invalid")
|
||||
}
|
||||
}
|
||||
if err := validateWorkFindingEvidence(f.Evidence, len(f.Sources)); err != nil {
|
||||
return err
|
||||
}
|
||||
if (f.Kind == "analysis" || f.Kind == "provisional") && strings.TrimSpace(f.Assumptions) == "" {
|
||||
return fmt.Errorf("checkpoint.assumptions: required")
|
||||
}
|
||||
}
|
||||
b, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(b) > 16*1024 {
|
||||
return fmt.Errorf("checkpoint.size: exceeds 16KiB")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s TaskSnapshot) SummaryBody() []UINode {
|
||||
labels := map[string]string{"queued": "排队中", "running": "进行中", "awaiting_user": "需要你处理", "completed": "已完成", "failed": "执行失败", "interrupted": "已中断"}
|
||||
var lines []string
|
||||
for _, f := range s.Findings {
|
||||
if f.Status == "active" {
|
||||
line := f.Text
|
||||
if f.Impact != "" {
|
||||
line += ";" + f.Impact
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
note := strings.Join(lines, "\n")
|
||||
if note == "" {
|
||||
note = TaskSummaryNoFindingsYet
|
||||
if s.ExecutionState == "queued" {
|
||||
note = "尚未开始处理,等待执行资源"
|
||||
}
|
||||
if s.Terminal() {
|
||||
note = s.Conclusion
|
||||
if note == "" {
|
||||
note = "没有可用阶段成果"
|
||||
}
|
||||
}
|
||||
}
|
||||
v := TaskSummaryView{Focus: s.Focus, FindingsNote: note, Remaining: s.Remaining, NextStep: s.NextStep, OutcomeLine: s.Conclusion}
|
||||
var body []UINode
|
||||
if s.Terminal() {
|
||||
body = v.FinishedBody(labels[s.ExecutionState], nil)
|
||||
} else {
|
||||
body = v.RunningBody(labels[s.ExecutionState], nil)
|
||||
}
|
||||
if s.Coverage != "" {
|
||||
body = append(body, UINode{Type: UIKV, KV: [][2]string{{"提供范围", s.Coverage}}})
|
||||
}
|
||||
if len(s.Limitations) > 0 {
|
||||
body = append(body, UINode{Type: UIList, Title: "范围与限制", Items: s.Limitations})
|
||||
}
|
||||
for _, r := range s.ResultRefs {
|
||||
if r.ArtifactID != "" && r.Availability == "available" {
|
||||
body = append(body, UINode{Type: UIDocRef, DocID: r.ArtifactID, Title: r.Name})
|
||||
}
|
||||
}
|
||||
return body
|
||||
}
|
||||
Reference in New Issue
Block a user