170 lines
6.2 KiB
Go
170 lines
6.2 KiB
Go
package agents_test
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"g.fc.run/misc/byre/internal/api"
|
|
"g.fc.run/misc/byre/internal/store"
|
|
)
|
|
|
|
func leadIdle(ev api.Event) bool {
|
|
return ev.Kind == "session.state" && ev.Owner != nil && ev.Payload["state"] == string(api.AgentSessionStateIdle)
|
|
}
|
|
|
|
func TestLeadSignalsAndShow(t *testing.T) {
|
|
t.Parallel()
|
|
e := newEnv(t)
|
|
m := e.manager()
|
|
|
|
lead, err := m.MessageLead(e.ctx, e.user, "Reply with the single word ready.", testModel, nil)
|
|
if err != nil {
|
|
t.Fatalf("message lead: %v", err)
|
|
}
|
|
if lead.Owner != login || lead.TaskId != "" || lead.Role != api.Lead {
|
|
t.Fatalf("lead session %+v", lead)
|
|
}
|
|
e.waitFor("lead idle", leadIdle)
|
|
|
|
worker := api.AgentSession{
|
|
Id: store.NewID(), TaskId: e.task.Id, Vendor: api.Claude, VendorSessionId: store.NewID(),
|
|
State: api.AgentSessionStateIdle, Role: api.Implementer, CreatedBy: login, CreatedAt: time.Now().UTC(),
|
|
}
|
|
err = e.store.CreateSession(e.ctx, worker, e.project.Id)
|
|
if err != nil {
|
|
t.Fatalf("create worker session: %v", err)
|
|
}
|
|
err = e.store.CreateInboxItem(e.ctx, api.InboxItem{
|
|
Id: store.NewID(), ProjectId: e.project.Id, TaskId: e.task.Id, AgentSessionId: worker.Id, ThreadId: "t1",
|
|
Kind: api.Report, Priority: api.Fyi, Payload: map[string]any{"text": "Finished the parser. The tests pass."},
|
|
State: api.Open, CreatedAt: time.Now().UTC(), Responses: []api.InboxResponse{},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("file report: %v", err)
|
|
}
|
|
created := e.waitFor("inbox.created", func(ev api.Event) bool { return ev.Kind == "inbox.created" })
|
|
m.Observe(created[len(created)-1])
|
|
|
|
signalled := e.waitFor("signal delivered", func(ev api.Event) bool {
|
|
return ev.Kind == "user.message" && ev.Owner != nil && ev.Actor == "byre"
|
|
})
|
|
text := signalled[len(signalled)-1].Payload["text"].(string)
|
|
if !strings.Contains(text, "Finished the parser") || !strings.Contains(text, e.task.Id) {
|
|
t.Fatalf("signal text %q", text)
|
|
}
|
|
e.waitFor("lead idle after signal", leadIdle)
|
|
|
|
_, err = m.MessageLead(e.ctx, e.user, "Call the show tool with task_id "+e.task.Id+", tabs [\"inbox\"] and tab \"inbox\". Then say done.", "", nil)
|
|
if err != nil {
|
|
t.Fatalf("message lead: %v", err)
|
|
}
|
|
e.waitFor("focus.updated by the lead", func(ev api.Event) bool {
|
|
return ev.Kind == "focus.updated" && ev.Actor == login+" (lead)"
|
|
})
|
|
f, err := e.store.GetFocus(e.ctx, login)
|
|
if err != nil {
|
|
t.Fatalf("get focus: %v", err)
|
|
}
|
|
if f.TaskId == nil || *f.TaskId != e.task.Id || f.ProjectId == nil || *f.ProjectId != e.project.Id || f.Tab != api.TabInbox {
|
|
t.Fatalf("focus %+v", f)
|
|
}
|
|
e.waitFor("lead idle after show", leadIdle)
|
|
|
|
m.SetPaneFocus(login, true)
|
|
_, err = m.MessageLead(e.ctx, e.user, "Call the show tool with task_id "+e.task.Id+", tabs [\"changes\"] and tab \"changes\". Then say done.", "", nil)
|
|
if err != nil {
|
|
t.Fatalf("message lead: %v", err)
|
|
}
|
|
e.waitFor("show refused", func(ev api.Event) bool {
|
|
return ev.Kind == "agent.tool_result" && ev.Owner != nil && strings.Contains(fmt.Sprint(ev.Payload), "working in the right pane")
|
|
})
|
|
e.waitFor("lead idle after refused show", leadIdle)
|
|
f, err = e.store.GetFocus(e.ctx, login)
|
|
if err != nil {
|
|
t.Fatalf("get focus: %v", err)
|
|
}
|
|
if f.Tab != api.TabInbox {
|
|
t.Fatalf("focus moved while the pane was focused: %+v", f)
|
|
}
|
|
|
|
_, err = m.MessageLead(e.ctx, e.user, "Call the show tool with task_id "+e.task.Id+", tabs [\"inbox\", \"changes\"] and tab \"inbox\". Then say done.", "", nil)
|
|
if err != nil {
|
|
t.Fatalf("message lead: %v", err)
|
|
}
|
|
e.waitFor("show of what is already shown", func(ev api.Event) bool {
|
|
return ev.Kind == "agent.tool_result" && ev.Owner != nil && strings.Contains(fmt.Sprint(ev.Payload), "Already shown")
|
|
})
|
|
e.waitFor("lead idle after already-shown show", leadIdle)
|
|
f, err = e.store.GetFocus(e.ctx, login)
|
|
if err != nil {
|
|
t.Fatalf("get focus: %v", err)
|
|
}
|
|
if f.Tab != api.TabInbox || !slices.Equal(f.Tabs, []api.TaskTab{api.TabInbox, api.TabChanges}) {
|
|
t.Fatalf("tabs not reordered on the shown tab: %+v", f)
|
|
}
|
|
|
|
err = e.store.WithdrawItem(e.ctx, created[len(created)-1].Payload["id"].(string))
|
|
if err != nil {
|
|
t.Fatalf("withdraw report: %v", err)
|
|
}
|
|
_, err = m.MessageLead(e.ctx, e.user, "Call the show tool with task_id "+e.task.Id+", tabs [\"changes\"] and tab \"changes\". Then say done.", "", nil)
|
|
if err != nil {
|
|
t.Fatalf("message lead: %v", err)
|
|
}
|
|
e.waitFor("show allowed on an emptied inbox", func(ev api.Event) bool {
|
|
return ev.Kind == "focus.updated" && ev.Actor == login+" (lead)" && ev.Payload["tab"] == string(api.TabChanges)
|
|
})
|
|
e.waitFor("lead idle after show on an emptied inbox", leadIdle)
|
|
|
|
err = m.SetFocus(e.ctx, login, api.Focus{ProjectId: &e.project.Id, Tabs: []api.TaskTab{api.TabProject}, Tab: api.TabProject}, login)
|
|
if err != nil {
|
|
t.Fatalf("focus project: %v", err)
|
|
}
|
|
_, err = m.MessageLead(e.ctx, e.user, "Call the show tool with task_id "+e.task.Id+", tabs [\"transcript\"] and tab \"transcript\". Then say done.", "", nil)
|
|
if err != nil {
|
|
t.Fatalf("message lead: %v", err)
|
|
}
|
|
e.waitFor("show allowed over the project tab", func(ev api.Event) bool {
|
|
return ev.Kind == "focus.updated" && ev.Actor == login+" (lead)" && ev.Payload["tab"] == string(api.TabTranscript)
|
|
})
|
|
}
|
|
|
|
func TestFocusProject(t *testing.T) {
|
|
t.Parallel()
|
|
e := newEnv(t)
|
|
m := e.manager()
|
|
|
|
err := m.SetFocus(e.ctx, login, api.Focus{ProjectId: &e.project.Id, Tabs: []api.TaskTab{api.TabProject}, Tab: api.TabProject}, login)
|
|
if err != nil {
|
|
t.Fatalf("focus project: %v", err)
|
|
}
|
|
f, err := e.store.GetFocus(e.ctx, login)
|
|
if err != nil {
|
|
t.Fatalf("get focus: %v", err)
|
|
}
|
|
if f.TaskId != nil || f.ProjectId == nil || *f.ProjectId != e.project.Id || f.Tab != api.TabProject {
|
|
t.Fatalf("focus %+v", f)
|
|
}
|
|
|
|
other := "nope"
|
|
err = m.SetFocus(e.ctx, login, api.Focus{TaskId: &e.task.Id, ProjectId: &other, Tabs: []api.TaskTab{}, Tab: api.TabTranscript}, login)
|
|
if err != nil {
|
|
t.Fatalf("focus task: %v", err)
|
|
}
|
|
f, err = e.store.GetFocus(e.ctx, login)
|
|
if err != nil {
|
|
t.Fatalf("get focus: %v", err)
|
|
}
|
|
if f.TaskId == nil || *f.TaskId != e.task.Id || f.ProjectId == nil || *f.ProjectId != e.project.Id {
|
|
t.Fatalf("focus %+v", f)
|
|
}
|
|
|
|
err = m.SetFocus(e.ctx, login, api.Focus{ProjectId: &other, Tabs: []api.TaskTab{}, Tab: api.TabProject}, login)
|
|
if err == nil {
|
|
t.Fatal("focus on an unknown project was accepted")
|
|
}
|
|
}
|