completion.go RawHistoryFolder
1package subagents
2
3import (
4 "context"
5 "fmt"
6 "strings"
7 "sync"
8)
9
10// Completion is one resident child's terminal turn outcome.
11type Completion struct {
12 AgentID string
13 Status string
14 Task string
15 Error string
16 Summary string
17}
18
19// CompletionTracker coordinates resident child completions with the parent
20// turn. It has no process or worker-event dependency.
21type CompletionTracker struct {
22 mu sync.Mutex
23 pending int
24 ready []Completion
25 changed chan struct{}
26}
27
28func NewCompletionTracker() *CompletionTracker {
29 return &CompletionTracker{changed: make(chan struct{})}
30}
31
32func (t *CompletionTracker) TrackResident() {
33 if t == nil {
34 return
35 }
36 t.mu.Lock()
37 t.pending++
38 t.signalLocked()
39 t.mu.Unlock()
40}
41
42func (t *CompletionTracker) Report(completion Completion) {
43 if t == nil {
44 return
45 }
46 t.mu.Lock()
47 if t.pending == 0 {
48 t.mu.Unlock()
49 return
50 }
51 t.pending--
52 t.ready = append(t.ready, completion)
53 t.signalLocked()
54 t.mu.Unlock()
55}
56
57func (t *CompletionTracker) Pending() int {
58 if t == nil {
59 return 0
60 }
61 t.mu.Lock()
62 defer t.mu.Unlock()
63 return t.pending
64}
65
66// Reset drops buffered outcomes at a parent-turn cancellation boundary.
67func (t *CompletionTracker) Reset() {
68 if t == nil {
69 return
70 }
71 t.mu.Lock()
72 t.pending = 0
73 t.ready = nil
74 t.signalLocked()
75 t.mu.Unlock()
76}
77
78func (t *CompletionTracker) WaitIdle(ctx context.Context) ([]Completion, error) {
79 if t == nil {
80 return nil, nil
81 }
82 for {
83 t.mu.Lock()
84 if t.pending == 0 {
85 ready := append([]Completion(nil), t.ready...)
86 t.ready = nil
87 t.mu.Unlock()
88 return ready, nil
89 }
90 changed := t.changed
91 t.mu.Unlock()
92 select {
93 case <-ctx.Done():
94 return nil, ctx.Err()
95 case <-changed:
96 }
97 }
98}
99
100func (t *CompletionTracker) signalLocked() { close(t.changed); t.changed = make(chan struct{}) }
101
102func FormatCompletionUpdate(batch []Completion, instruction string) string {
103 if len(batch) == 0 {
104 return ""
105 }
106 var b strings.Builder
107 b.WriteString("[auto-subagents update]\n")
108 for _, completion := range batch {
109 fmt.Fprintf(&b, "- %s: %s", completion.AgentID, completion.Status)
110 if completion.Error != "" {
111 fmt.Fprintf(&b, " (%s)", completion.Error)
112 }
113 if completion.Task != "" {
114 fmt.Fprintf(&b, " — %s", completion.Task)
115 }
116 if completion.Summary != "" {
117 fmt.Fprintf(&b, "\n final: %s", completion.Summary)
118 }
119 b.WriteByte('\n')
120 }
121 if instruction != "" {
122 b.WriteString(instruction)
123 }
124 return strings.TrimSpace(b.String())
125}