v0.4.4 e5506c1

cli_session_test.go RawHistoryFolder

1package agent
2
3import (
4	"os"
5	"path/filepath"
6	"testing"
7
8	"github.com/patriceckhart/zot/packages/agent/modes"
9	"github.com/patriceckhart/zot/packages/core"
10	"github.com/patriceckhart/zot/packages/provider"
11)
12
13func TestLiveInteractiveAgentUsesReplacementAgentForSessionResume(t *testing.T) {
14	startup := core.NewAgent(nil, "startup-model", "", nil)
15	startup.SetMessages([]provider.Message{{
16		Role:    provider.RoleUser,
17		Content: []provider.Content{provider.TextBlock{Text: "startup transcript"}},
18	}})
19
20	replacement := core.NewAgent(nil, "replacement-model", "", nil)
21	iv := modes.NewInteractive(modes.InteractiveConfig{Agent: replacement})
22
23	resumed := []provider.Message{{
24		Role:    provider.RoleUser,
25		Content: []provider.Content{provider.TextBlock{Text: "resumed transcript"}},
26	}}
27	liveInteractiveAgent(iv, startup).SetMessages(resumed)
28
29	if got := firstMessageText(replacement.Messages()); got != "resumed transcript" {
30		t.Fatalf("replacement agent transcript = %q, want resumed transcript", got)
31	}
32	if got := firstMessageText(startup.Messages()); got != "startup transcript" {
33		t.Fatalf("startup agent transcript changed to %q", got)
34	}
35}
36
37func TestBindAgentSession(t *testing.T) {
38	ag := core.NewAgent(nil, "@preset/flash", "", nil)
39	bindAgentSession(ag, &core.Session{ID: "sess-xyz"})
40	if ag.SessionID != "sess-xyz" {
41		t.Fatalf("SessionID = %q; want sess-xyz", ag.SessionID)
42	}
43	bindAgentSession(ag, &core.Session{ID: "other"})
44	if ag.SessionID != "other" {
45		t.Fatalf("SessionID = %q; want other after rebind", ag.SessionID)
46	}
47	bindAgentSession(ag, nil)
48	if ag.SessionID != "other" {
49		t.Fatalf("nil session mutated SessionID to %q", ag.SessionID)
50	}
51}
52
53func TestRotateInteractiveSessionPreservesOldTranscriptAndResetsAgent(t *testing.T) {
54	root := t.TempDir()
55	cwd := t.TempDir()
56	old, err := core.NewSession(root, cwd, "test-provider", "old-model", "test")
57	if err != nil {
58		t.Fatal(err)
59	}
60	ag := core.NewAgent(nil, "new-model", "", nil)
61	ag.SetMessages([]provider.Message{{
62		Role:    provider.RoleUser,
63		Content: []provider.Content{provider.TextBlock{Text: "keep this conversation"}},
64	}})
65	ag.SeedCost(provider.Usage{InputTokens: 12, CostUSD: 0.25})
66	ag.SeedLastTurnUsage(provider.Usage{InputTokens: 12})
67	ag.QueueMessage("drop queued prompt")
68
69	fresh, err := rotateInteractiveSession(root, cwd, "test-provider", "new-model", "test", ag, old, 0)
70	if err != nil {
71		t.Fatal(err)
72	}
73	defer fresh.Close()
74	if fresh.ID == old.ID || fresh.Path == old.Path {
75		t.Fatalf("session was not rotated: old=%s new=%s", old.Path, fresh.Path)
76	}
77	if ag.SessionID != fresh.ID {
78		t.Fatalf("agent session ID = %q, want %q", ag.SessionID, fresh.ID)
79	}
80	if len(ag.Messages()) != 0 {
81		t.Fatalf("new transcript is not empty: %+v", ag.Messages())
82	}
83	if got := ag.Cost(); got != (provider.Usage{}) {
84		t.Fatalf("new session cost = %+v, want zero", got)
85	}
86	if got := ag.LastTurnUsage(); got != (provider.Usage{}) {
87		t.Fatalf("new session last usage = %+v, want zero", got)
88	}
89	if got := ag.QueuedMessageCount(); got != 0 {
90		t.Fatalf("queued messages = %d, want zero", got)
91	}
92
93	persisted, messages, err := core.OpenSession(old.Path)
94	if err != nil {
95		t.Fatalf("open previous session: %v", err)
96	}
97	defer persisted.Close()
98	if got := firstMessageText(messages); got != "keep this conversation" {
99		t.Fatalf("previous transcript = %q", got)
100	}
101}
102
103func TestRotateInteractiveSessionCreationFailureLeavesAgentUntouched(t *testing.T) {
104	ag := core.NewAgent(nil, "model", "", nil)
105	ag.SetMessages([]provider.Message{{Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "still active"}}}})
106	blockedRoot := filepath.Join(t.TempDir(), "file")
107	if err := os.WriteFile(blockedRoot, []byte("not a directory"), 0o600); err != nil {
108		t.Fatal(err)
109	}
110
111	if _, err := rotateInteractiveSession(blockedRoot, t.TempDir(), "test", "model", "test", ag, nil, 0); err == nil {
112		t.Fatal("rotateInteractiveSession returned nil error for invalid root")
113	}
114	if got := firstMessageText(ag.Messages()); got != "still active" {
115		t.Fatalf("agent transcript changed to %q after failed rotation", got)
116	}
117}
118
119func TestLiveInteractiveAgentFallsBackBeforeInteractiveConstruction(t *testing.T) {
120	startup := core.NewAgent(nil, "startup-model", "", nil)
121	if got := liveInteractiveAgent(nil, startup); got != startup {
122		t.Fatalf("fallback agent = %p, want %p", got, startup)
123	}
124}
125
126func firstMessageText(messages []provider.Message) string {
127	if len(messages) == 0 || len(messages[0].Content) == 0 {
128		return ""
129	}
130	text, _ := messages[0].Content[0].(provider.TextBlock)
131	return text.Text
132}

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 476

↑↓ moveenter openesc close