v0.51.0 a9d674e

resident_child_test.go RawHistoryFolder

1package subagents
2
3import (
4	"context"
5	"strings"
6	"testing"
7	"time"
8
9	"github.com/bnema/zut/packages/core"
10)
11
12func TestJournaledResidentChildReceivesLiveJournalEvents(t *testing.T) {
13	journal, err := OpenResidentJournal(t.TempDir(), "live-child")
14	if err != nil {
15		t.Fatal(err)
16	}
17	child := newJournaledResidentChild(ResidentChildSpec{}, journal, ResidentTurnRunner(func(context.Context, string) error { return nil }), nil)
18	defer child.Close(context.Background())
19	oldActivity := time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
20	child.mu.Lock()
21	child.activityUpdatedAt = oldActivity
22	child.mu.Unlock()
23	child.live.Start("turn-1")
24	if err := journal.RecordAgentEvent(core.EvTextDelta{Delta: "streaming"}); err != nil {
25		t.Fatal(err)
26	}
27	if got := child.Live(); got.AssistantText != "streaming" || got.Revision == 0 {
28		t.Fatalf("live child = %#v", got)
29	}
30	if got := child.ActivityUpdatedAt(); !got.After(oldActivity) {
31		t.Fatalf("activity time = %s, want after %s", got, oldActivity)
32	}
33}
34
35func TestResidentChildRunsAcceptedPromptsFIFOWithOneActiveTurn(t *testing.T) {
36	started := make(chan string, 2)
37	releaseFirst := make(chan struct{})
38	completed := make(chan string, 2)
39	child := NewResidentChild(ResidentTurnRunner(func(ctx context.Context, prompt string) error {
40		started <- prompt
41		if prompt == "first" {
42			select {
43			case <-releaseFirst:
44			case <-ctx.Done():
45				return ctx.Err()
46			}
47		}
48		completed <- prompt
49		return nil
50	}))
51	defer child.Close(context.Background())
52
53	if err := child.Resume(context.Background(), "first"); err != nil {
54		t.Fatalf("first Resume: %v", err)
55	}
56	if err := child.Resume(context.Background(), "second"); err != nil {
57		t.Fatalf("second Resume: %v", err)
58	}
59	select {
60	case got := <-started:
61		if got != "first" {
62			t.Fatalf("first start = %q", got)
63		}
64	case <-time.After(time.Second):
65		t.Fatal("first turn did not start")
66	}
67	select {
68	case got := <-started:
69		t.Fatalf("second turn started before first completed: %q", got)
70	default:
71	}
72	close(releaseFirst)
73	for _, want := range []string{"first", "second"} {
74		select {
75		case got := <-completed:
76			if got != want {
77				t.Fatalf("completion = %q, want %q", got, want)
78			}
79		case <-time.After(time.Second):
80			t.Fatalf("%s turn did not complete", want)
81		}
82	}
83}
84
85func TestResidentChildReportsAndPreservesFailureWhenTurnStartCannotPersist(t *testing.T) {
86	journal, err := OpenResidentJournal(t.TempDir(), "start-persistence-failure")
87	if err != nil {
88		t.Fatal(err)
89	}
90	if err := journal.Close(); err != nil {
91		t.Fatal(err)
92	}
93	completed := make(chan ResidentCompletion, 1)
94	run := make(chan struct{}, 1)
95	child := newJournaledResidentChild(
96		ResidentChildSpec{ID: "start-persistence-failure"},
97		journal,
98		ResidentTurnRunner(func(context.Context, string) error {
99			run <- struct{}{}
100			return nil
101		}),
102		func(completion ResidentCompletion) { completed <- completion },
103	)
104
105	request := residentPrompt{turnID: "turn", prompt: "task", ack: make(chan error, 1)}
106	select {
107	case child.inbox <- request:
108	case <-time.After(time.Second):
109		t.Fatal("child did not accept test prompt")
110	}
111	select {
112	case completion := <-completed:
113		if completion.ChildID != "start-persistence-failure" || completion.TurnID != "turn" || completion.Task != "task" {
114			t.Fatalf("completion = %#v", completion)
115		}
116		if completion.Err == nil || !strings.Contains(completion.Err.Error(), "persist resident child start state") {
117			t.Fatalf("completion error = %v", completion.Err)
118		}
119	case <-time.After(time.Second):
120		t.Fatal("failed turn did not report completion")
121	}
122	select {
123	case <-child.done:
124	case <-time.After(time.Second):
125		t.Fatal("child did not stop after start persistence failure")
126	}
127	if state := child.State(); state != ResidentFailed {
128		t.Fatalf("state = %q, want %q", state, ResidentFailed)
129	}
130	select {
131	case <-run:
132		t.Fatal("runner started after turn-start persistence failure")
133	default:
134	}
135}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 477

↑↓ moveenter openesc close