okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

Aly Raffauffix(stream): restore unfinished responses on session attachmenta229862

main
3.4 KiB86 linesraw
1// These regressions exercise event ordering and row identity inside the TUI,
2// which daemon E2E cannot inspect or deliver twice at a chosen boundary.
3package tui
4
5import (
6	"testing"
7
8	"albedo/cli/internal/daemon"
9)
10
11func TestEmptyProviderRecordsDoNotDuplicateStreamedReply(t *testing.T) {
12	for _, settled := range []bool{false, true} {
13		model := newTestChatModel(t, &daemon.Session{ID: "parent"})
14		model.SetSize(120, 30)
15		model.handleStreamEvent(daemon.StreamEvent{Type: daemon.EventUser, Text: "launch a child", Source: "chat"})
16		reply := "Launched a subagent to write the Python love song."
17		model.handleStreamEvent(daemon.StreamEvent{Type: daemon.EventText, Text: reply, MessageID: "live-reply"})
18		if settled {
19			model.handleStreamEvent(daemon.StreamEvent{Type: daemon.EventUsage})
20		}
21		for _, id := range []string{"2-provider", "3-provider"} {
22			model.handleStreamEvent(daemon.StreamEvent{Type: daemon.EventMessage, EntryID: id})
23		}
24		final := daemon.StreamEvent{Type: daemon.EventMessage, EntryID: "4", Position: 4, Text: reply}
25		model.handleStreamEvent(final)
26		model.handleStreamEvent(daemon.StreamEvent{Type: daemon.EventCommitted, Seq: 4, ReplacesLiveIDs: []string{"live-reply"}})
27		model.handleStreamEvent(final)
28		model.handleStreamEvent(daemon.StreamEvent{Type: daemon.EventTurnCompleted})
29		count, footers := 0, 0
30		for _, entry := range model.History.Entries() {
31			if entry.Kind == EntryAssistant {
32				if entry.Text != reply || entry.ID != "4" {
33					t.Fatalf("wrong saved reply: %#v", entry)
34				}
35				count++
36			}
37			if entry.Kind == EntryTurnEnd {
38				footers++
39			}
40		}
41		if count != 1 || footers != 1 {
42			t.Fatalf("settled=%v: got %d replies and %d footers", settled, count, footers)
43		}
44	}
45}
46
47func TestMailIdentityPreservesDistinctMessagesAndTurnOwnership(t *testing.T) {
48	model := newTestChatModel(t, &daemon.Session{ID: "parent"})
49	model.SetSize(100, 30)
50	model.handleStreamEvent(daemon.StreamEvent{Type: daemon.EventUser, Source: "chat", Text: "start"})
51	model.handleStreamEvent(daemon.StreamEvent{Type: daemon.EventText, Text: "working"})
52	turn := model.transcript.turn
53	mail := daemon.StreamEvent{Type: daemon.EventUser, Source: "mail", Speaker: "You", MailKind: "result", EntryID: "7", Position: 7, Text: "finished"}
54	model.handleStreamEvent(mail)
55	if model.transcript.turn != turn {
56		t.Fatal("a sender named You reopened the parent's turn")
57	}
58	model.handleStreamEvent(mail)
59	mail.Text = "finished with evidence"
60	model.handleStreamEvent(mail)
61	mail.EntryID, mail.Position = "8", 8
62	model.handleStreamEvent(mail)
63	count := 0
64	for _, entry := range model.History.Entries() {
65		if entry.Source == "mail" {
66			count++
67			if entry.Speaker != "You" || entry.Text != mail.Text {
68				t.Fatalf("mail update lost attribution: %#v", entry)
69			}
70		}
71	}
72	if count != 2 {
73		t.Fatalf("distinct identical letters must remain distinct, got %d", count)
74	}
75	model.loadingOlder = true
76	model.showOlder(ChatOlderLoadedMsg{SessionID: model.SessionID, Generation: model.Generation, Page: &daemon.HistoryPage{Events: []daemon.StreamEvent{mail}, Before: 8}})
77	if model.History.Len() != 4 {
78		t.Fatalf("overlapping history added rows: %#v", model.History.Entries())
79	}
80	model.handleStreamEvent(daemon.StreamEvent{Type: daemon.EventReset})
81	mail.Replayed = true
82	model.handleStreamEvent(mail)
83	if model.History.Len() != 1 || model.History.Entries()[0].Speaker != "You" {
84		t.Fatal("reset failed to replace the transcript with attributed history")
85	}
86}