110 lines
3.0 KiB
Go
110 lines
3.0 KiB
Go
package workflow
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
|
|
"codex-agent-manager/internal/runtime"
|
|
)
|
|
|
|
type Store struct {
|
|
WorkspaceRoot string
|
|
Runtime RuntimeReader
|
|
}
|
|
|
|
func (s Store) View() (View, error) {
|
|
snapshot, err := s.Runtime.Snapshot()
|
|
if err != nil {
|
|
return View{}, err
|
|
}
|
|
view := View{
|
|
Events: []Event{},
|
|
HandoffEdges: []HandoffEdge{},
|
|
Phases: []Phase{},
|
|
Source: SourceEvidence{Kind: snapshot.Source.Kind, Path: snapshot.Source.Path, Confidence: snapshot.Source.Confidence, Message: snapshot.Source.Message},
|
|
}
|
|
for _, thread := range snapshot.Threads {
|
|
view.Events = append(view.Events, Event{
|
|
Kind: "thread",
|
|
Label: thread.Role,
|
|
ThreadID: thread.ID,
|
|
OccurredAt: thread.CreatedAt,
|
|
Source: fromRuntimeSource(thread.Source),
|
|
})
|
|
}
|
|
for _, edge := range snapshot.SpawnEdges {
|
|
source := fromRuntimeSource(edge.Source)
|
|
view.Events = append(view.Events, Event{
|
|
Kind: "handoff",
|
|
Label: edge.Reason,
|
|
ThreadID: edge.FromThreadID,
|
|
RelatedID: edge.ToThreadID,
|
|
OccurredAt: edge.CreatedAt,
|
|
Source: source,
|
|
})
|
|
view.HandoffEdges = append(view.HandoffEdges, HandoffEdge{
|
|
FromThreadID: edge.FromThreadID,
|
|
ToThreadID: edge.ToThreadID,
|
|
Label: edge.Reason,
|
|
Source: source,
|
|
})
|
|
}
|
|
for _, goal := range snapshot.Goals {
|
|
view.Events = append(view.Events, Event{
|
|
Kind: "goal",
|
|
Label: goal.Status,
|
|
ThreadID: goal.ThreadID,
|
|
RelatedID: goal.Goal,
|
|
OccurredAt: goal.UpdatedAt,
|
|
Source: fromRuntimeSource(goal.Source),
|
|
})
|
|
}
|
|
planEvents, phases := readPlanEvidence(s.WorkspaceRoot)
|
|
view.Events = append(view.Events, planEvents...)
|
|
view.Phases = phases
|
|
sort.SliceStable(view.Events, func(i, j int) bool {
|
|
if view.Events[i].OccurredAt == view.Events[j].OccurredAt {
|
|
return view.Events[i].Kind < view.Events[j].Kind
|
|
}
|
|
return view.Events[i].OccurredAt < view.Events[j].OccurredAt
|
|
})
|
|
return view, nil
|
|
}
|
|
|
|
func fromRuntimeSource(source runtime.SourceEvidence) SourceEvidence {
|
|
return SourceEvidence{
|
|
Kind: source.Kind,
|
|
Path: source.Path,
|
|
Confidence: source.Confidence,
|
|
Message: source.Message,
|
|
}
|
|
}
|
|
|
|
func readPlanEvidence(root string) ([]Event, []Phase) {
|
|
path := filepath.Join(root, "task_plan.md")
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return []Event{}, []Phase{}
|
|
}
|
|
source := SourceEvidence{Kind: "plan_file", Path: path, Confidence: "medium"}
|
|
events := []Event{{Kind: "plan_file", Label: filepath.Base(path), Source: source}}
|
|
var phases []Phase
|
|
re := regexp.MustCompile(`^\|\s*([^|]+?)\s*\|\s*([A-Za-z_]+)\s*\|`)
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
match := re.FindStringSubmatch(line)
|
|
if len(match) != 3 {
|
|
continue
|
|
}
|
|
name := strings.TrimSpace(match[1])
|
|
status := strings.TrimSpace(match[2])
|
|
if strings.EqualFold(name, "Phase") || strings.EqualFold(status, "Status") {
|
|
continue
|
|
}
|
|
phases = append(phases, Phase{Name: name, Status: status, Source: source})
|
|
}
|
|
return events, phases
|
|
}
|