resident_subagents_dialog_test.go RawHistoryFolder
1package modes
2
3import (
4 "context"
5 "fmt"
6 "slices"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/bnema/zut/packages/agent/subagents"
12 "github.com/bnema/zut/packages/core"
13 "github.com/bnema/zut/packages/provider"
14 "github.com/bnema/zut/packages/tui"
15)
16
17func TestResidentSubagentListUsesHumanLabelTerminalStateAndRelativeTime(t *testing.T) {
18 row := subagents.ResidentSnapshot{ID: "b6382ac7-ca36-45f1-9549-dd9ee4d195b7", Profile: "go-reviewer", Model: "gpt-5.6-luna", State: subagents.ResidentIdle}
19 if got := residentDisplayName(row); got != "go-reviewer" {
20 t.Fatalf("display name = %q", got)
21 }
22 if got := residentDisplayState(row.State); got != "completed" {
23 t.Fatalf("display state = %q", got)
24 }
25 if got := shortResidentID(row.ID); got != "b6382ac7" {
26 t.Fatalf("short ID = %q", got)
27 }
28 if got := formatResidentUpdatedAt(time.Date(2026, time.August, 13, 12, 0, 0, 0, time.UTC), time.Date(2026, time.August, 13, 12, 2, 0, 0, time.UTC)); got != "2m ago" {
29 t.Fatalf("relative update = %q", got)
30 }
31}
32
33func TestResidentSubagentsDialogShowsUsageMetadata(t *testing.T) {
34 completed := make(chan struct{}, 2)
35 manager := subagents.NewResidentManager(t.TempDir(), func(_ subagents.ResidentChildSpec, journal *subagents.ResidentJournal) (subagents.ResidentRuntime, error) {
36 journal.ConfigureUsage(272_000, true)
37 return subagents.ResidentTurnRunner(func(context.Context, string) error {
38 usage := provider.Usage{InputTokens: 84_000, OutputTokens: 1_500, CacheReadTokens: 123_000, CacheMeasuredPromptTokens: 207_000, CacheMeasuredReadTokens: 123_000, CostUSD: 0.525}
39 return journal.RecordAgentEvent(core.EvUsage{Usage: usage, Cumulative: usage})
40 }), nil
41 })
42 manager.SetCompletionObserver(func(subagents.ResidentCompletion) bool { completed <- struct{}{}; return true })
43 t.Cleanup(func() { _ = manager.Close(context.Background()) })
44 for index := range 2 {
45 id := fmt.Sprintf("usage-child-%d", index)
46 if _, err := manager.Spawn(context.Background(), subagents.ResidentChildSpec{ID: id, SessionID: "session-" + id, Profile: "reviewer", Provider: "openai-codex", Model: "gpt-test"}, "task"); err != nil {
47 t.Fatal(err)
48 }
49 }
50 for range 2 {
51 select {
52 case <-completed:
53 case <-time.After(time.Second):
54 t.Fatal("resident child did not complete")
55 }
56 }
57 dialog := newResidentSubagentsDialog()
58 dialog.Open(manager)
59 plain := strings.Join(plainResidentIndicatorLines(dialog.Render(tui.Dark, 100, 10)), "\n")
60 if !strings.Contains(plain, "\n ↑84k ↓1.5k R123k/ C59.4% $0.525 (sub) 76.1%/272k") {
61 t.Fatalf("dialog = %q, want usage metadata line", plain)
62 }
63 for _, height := range []int{3, 4} {
64 if lines := dialog.Render(tui.Dark, 100, height); len(lines) > height {
65 t.Fatalf("height %d rendered %d lines: %q", height, len(lines), plainResidentIndicatorLines(lines))
66 }
67 }
68 for width := 1; width <= 3; width++ {
69 for _, line := range plainResidentIndicatorLines(dialog.Render(tui.Dark, width, 10)) {
70 if line == " " {
71 t.Fatalf("width %d rendered an empty metadata row", width)
72 }
73 }
74 }
75}
76
77func TestResidentSubagentsDialogBoundsEmptyStateByHeight(t *testing.T) {
78 dialog := newResidentSubagentsDialog()
79 dialog.Open(nil)
80 tests := []struct {
81 height int
82 want []string
83 }{
84 {height: 0, want: nil},
85 {height: 1, want: []string{" Resident subagents"}},
86 {height: 2, want: []string{" Resident subagents", " Enter: open Esc: close /subagents new <task>: spawn"}},
87 {height: 3, want: []string{" Resident subagents", " Enter: open Esc: close /subagents new <task>: spawn", " No resident subagents."}},
88 }
89 for _, test := range tests {
90 if got := plainResidentIndicatorLines(dialog.Render(tui.Dark, 100, test.height)); !slices.Equal(got, test.want) {
91 t.Errorf("height %d rendered %q, want %q", test.height, got, test.want)
92 }
93 }
94}
95
96func TestResidentResultStatusIncludesFinalSummary(t *testing.T) {
97 got := residentResultStatus("child", subagents.ResidentResult{State: subagents.ResidentIdle, Summary: "the child answer"})
98 if got != "completed result child: the child answer" {
99 t.Fatalf("result status = %q", got)
100 }
101}
102
103func TestResidentResultStatusSanitizesDeprecatedHandoff(t *testing.T) {
104 // Archived v2 results may still carry a handoff; the status line must
105 // sanitize control sequences exactly like any other summary text.
106 got := residentResultStatus("child", subagents.ResidentResult{
107 State: subagents.ResidentFailed,
108 Handoff: "Workspace: /tmp/\x1b[31mrepo\x1b[0m\x1b]52;c;secret\a\r\x00",
109 })
110 if got != "failed result child: Workspace: /tmp/repo" {
111 t.Fatalf("result status = %q", got)
112 }
113}
114
115func TestResidentSubagentsDialogRefreshAllowsNilReceiver(t *testing.T) {
116 var dialog *residentSubagentsDialog
117 dialog.refresh(1)
118}