interactive_resident_completion_test.go RawHistoryFolder
1package modes
2
3import (
4 "context"
5 "errors"
6 "strings"
7 "testing"
8 "time"
9
10 "github.com/bnema/zut/packages/agent/subagents"
11 "github.com/bnema/zut/packages/core"
12)
13
14func TestResidentCompletionBatchDoesNotReportTerminalSiblingAsRunning(t *testing.T) {
15 ctx, cancel := context.WithCancel(context.Background())
16 defer cancel()
17 ag := core.NewAgent(nil, "model", "", nil)
18 i := &Interactive{agent: ag, busy: true, runCtx: ctx}
19 release := i.beginCompletionDeliveryHold()
20 defer release()
21 i.TrackResidentSubagent("first", "turn-1")
22 i.TrackResidentSubagent("second", "turn-2")
23
24 tracker := i.ensureCompletionTracker()
25 tracker.Report(subagents.Completion{AgentID: "first", TurnID: "turn-1", Status: "completed"})
26 tracker.Report(subagents.Completion{AgentID: "second", TurnID: "turn-2", Status: "completed"})
27 i.requestCompletionDelivery()
28
29 deadline := time.NewTimer(2 * time.Second)
30 defer deadline.Stop()
31 for {
32 queued := ag.PendingQueuedMessages()
33 if len(queued) == 2 {
34 for _, message := range queued {
35 if strings.Contains(message.Text, "Still running:") {
36 t.Fatalf("terminal sibling reported as running: %q", message.Text)
37 }
38 }
39 return
40 }
41 select {
42 case <-deadline.C:
43 t.Fatalf("queued completions = %#v, want 2", queued)
44 case <-time.After(time.Millisecond):
45 }
46 }
47}
48
49func TestResidentCompletionSlidesIntoBusyParent(t *testing.T) {
50 for _, tc := range []struct {
51 name string
52 err error
53 compacting bool
54 }{
55 {name: "success"},
56 {name: "failure", err: errors.New("review failed")},
57 {name: "compaction", compacting: true},
58 } {
59 t.Run(tc.name, func(t *testing.T) {
60 ctx, cancel := context.WithCancel(context.Background())
61 defer cancel()
62 ag := core.NewAgent(nil, "model", "", nil)
63 i := &Interactive{agent: ag, busy: true, compacting: tc.compacting, runCtx: ctx}
64 release := i.beginCompletionDeliveryHold()
65 defer release()
66 i.TrackResidentSubagent("finished", "turn-1")
67 i.TrackResidentSubagent("still-running", "turn-2")
68 i.ReportResidentSubagent(subagents.ResidentCompletion{ChildID: "finished", TurnID: "turn-1", Task: "review", Summary: "saved result", Err: tc.err})
69
70 deadline := time.NewTimer(2 * time.Second)
71 defer deadline.Stop()
72 tick := time.NewTicker(time.Millisecond)
73 defer tick.Stop()
74 for {
75 i.mu.Lock()
76 queued := ag.PendingQueuedMessages()
77 if tc.compacting {
78 queued = append([]core.QueuedMessage(nil), i.queued...)
79 }
80 i.mu.Unlock()
81 if len(queued) != 0 {
82 if len(queued) != 1 || !queued[0].HostEvent || !strings.Contains(queued[0].Text, "saved result") || !strings.Contains(queued[0].Text, "[auto-subagents update]") {
83 t.Fatalf("queued completions = %#v", queued)
84 }
85 if !strings.Contains(queued[0].Text, "Still running: still-running") {
86 t.Fatalf("completion update missing active sibling: %q", queued[0].Text)
87 }
88 if tc.err != nil && !strings.Contains(queued[0].Text, tc.err.Error()) {
89 t.Fatalf("missing failure: %q", queued[0].Text)
90 }
91 if i.ensureCompletionTracker().Pending() != 1 {
92 t.Fatal("unfinished child was not retained")
93 }
94 return
95 }
96 select {
97 case <-deadline.C:
98 t.Fatal("completion did not slide in while parent and sibling were active")
99 case <-tick.C:
100 }
101 }
102 })
103 }
104}