interactive_completion_delivery_test.go RawHistoryFolder
1package modes
2
3import (
4 "context"
5 "encoding/json"
6 "strings"
7 "testing"
8 "time"
9
10 "github.com/bnema/zut/packages/agent/subagents"
11 "github.com/bnema/zut/packages/agent/tools"
12 "github.com/bnema/zut/packages/core"
13 "github.com/bnema/zut/packages/provider"
14)
15
16func TestInteractiveWaitReportAcceptance(t *testing.T) {
17 for _, reset := range []bool{false, true} {
18 name := "accepted"
19 if reset {
20 name = "cancelled tracking"
21 }
22 t.Run(name, func(t *testing.T) {
23 ctx, cancel := context.WithCancel(t.Context())
24 defer cancel()
25 ag := core.NewAgent(nil, "test", "", nil)
26 i := &Interactive{agent: ag, busy: true, runCtx: ctx}
27 release := i.beginCompletionDeliveryHold()
28 defer release()
29 manager := subagents.NewResidentManager(t.TempDir(), func(_ subagents.ResidentChildSpec, journal *subagents.ResidentJournal) (subagents.ResidentRuntime, error) {
30 return subagents.ResidentTurnRunner(func(context.Context, string) error {
31 if reset {
32 i.cancelCoordinator()
33 }
34 return journal.RecordAgentEvent(core.EvAssistantMessage{Message: provider.Message{Role: provider.RoleAssistant, Content: []provider.Content{provider.TextBlock{Text: "interactive report"}}}})
35 }), nil
36 })
37 t.Cleanup(func() { _ = manager.Close(context.Background()) })
38 manager.SetAcceptedObserver(func(spec subagents.ResidentChildSpec, turn, _ string) { i.TrackResidentSubagent(spec.ID, turn) })
39 manager.SetCompletionObserver(i.ReportResidentSubagent)
40 spawn := &tools.SubagentSpawnTool{
41 ResidentManager: manager, Enabled: func() bool { return true },
42 BuildResidentSpec: func(context.Context, tools.ResidentSpawnRequest) (subagents.ResidentChildSpec, error) {
43 return subagents.ResidentChildSpec{ID: "worker", SessionID: "session", Provider: "openai", Model: "test"}, nil
44 },
45 }
46 result, err := spawn.Execute(ctx, json.RawMessage(`{"task":"report","wait":5}`), nil)
47 if err != nil || result.IsError {
48 t.Fatalf("spawn = (%#v, %v)", result, err)
49 }
50 encoded, err := json.Marshal(result.Content)
51 if err != nil {
52 t.Fatal(err)
53 }
54 if strings.Contains(string(encoded), "interactive report") != reset {
55 t.Fatalf("reset=%t tool result=%s", reset, encoded)
56 }
57 if reset {
58 if strings.Contains(string(encoded), "host_update") || ag.QueuedMessageCount() != 0 {
59 t.Fatalf("rejected completion claimed host delivery: %s", encoded)
60 }
61 return
62 }
63 deadline := time.NewTimer(5 * time.Second)
64 defer deadline.Stop()
65 tick := time.NewTicker(time.Millisecond)
66 defer tick.Stop()
67 for {
68 queued := ag.PendingQueuedMessages()
69 if len(queued) != 0 {
70 if len(queued) != 1 || !queued[0].HostEvent || strings.Count(queued[0].Text, "interactive report") != 1 || !strings.Contains(queued[0].Text, "[auto-subagents update]") {
71 t.Fatalf("queued host report = %#v", queued)
72 }
73 return
74 }
75 select {
76 case <-deadline.C:
77 t.Fatal("accepted completion did not slide in")
78 case <-tick.C:
79 }
80 }
81 })
82 }
83}