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 }