import (
"context"
- "encoding/json"
"errors"
"net/http"
"os"
}
}
-// DaemonMessageTest owns test agents and the loops selected by Start.
-type DaemonMessageTest struct {
- agents DaemonAgents
- loops []*TurnLoop
- events []*TurnEventRecorder
- messages *DaemonMessages
- logs *SyncBuffer
- cancel context.CancelFunc
- done []chan error
-}
-
-func (r *DaemonMessageTest) Start(t *testing.T, indices ...int) {
- t.Helper()
-
- ctx, cancel := context.WithCancel(t.Context())
- r.cancel = cancel
- for _, index := range indices {
- done := make(chan error, 1)
- r.done = append(r.done, done)
- go func() {
- done <- r.loops[index].Run(ctx)
- }()
- }
-}
-
-func (r *DaemonMessageTest) Queue(t *testing.T) DaemonMessageFile {
- t.Helper()
-
- data, err := os.ReadFile(r.messages.path)
- require.NoError(t, err)
-
- var file DaemonMessageFile
-
- require.NoError(t, json.Unmarshal(data, &file))
-
- return file
-}
-
-func (r *DaemonMessageTest) Close(t *testing.T) {
- t.Helper()
-
- if r.cancel != nil {
- r.cancel()
-
- for _, done := range r.done {
- require.ErrorIs(t, <-done, context.Canceled)
- }
-
- r.cancel = nil
- r.done = nil
- }
-
- require.NoError(t, r.agents.Close())
-
- r.agents = nil
-}
-
-func newDaemonMessageTest(t *testing.T, dir string, server *ModelFake) *DaemonMessageTest {
- t.Helper()
-
- _, err := os.Stat(filepath.Join(dir, daemonAgentDirName, "children"))
- if errors.Is(err, os.ErrNotExist) {
- daemonChildConfig(t, dir, "Garden")
- daemonChildConfig(t, dir, "Library")
- } else {
- require.NoError(t, err)
- }
-
- test := &DaemonMessageTest{
- logs: &SyncBuffer{},
+func TestChildScheduleWakesOnlyItsOwner(t *testing.T) {
+ server := newModelFake(t)
+ server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
+ modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
- test.agents, err = openDaemonAgents(t.Context(), dir, server.URL, ModelAPIKey(modelTestKey), defaultConfig().Compactor, newTestClock().Now, daemonTestLogger(test.logs))
- require.NoError(t, err)
- t.Cleanup(func() {
- test.Close(t)
- })
-
- for _, agent := range test.agents {
- events := &TurnEventRecorder{}
- loop, err := agent.Loop(nil, events.Add)
- require.NoError(t, err)
-
- test.events = append(test.events, events)
- test.loops = append(test.loops, loop)
+ test := newDaemonMessageTest(t, t.TempDir(), server)
+ child := test.agents[1]
+ at := child.clock().Add(time.Minute).Format(scheduleAtLayout)
+ output := test.loops[1].tools.run(t.Context(), "schedule_set", `{"text":"Check the plants.","at":"`+at+`","cron":null}`)
+ require.Contains(t, output, "Schedule 1 set.")
+ require.Equal(t, "Garden", child.schedules.sorted()[0].Owner)
+
+ child.schedules.clock = func() time.Time {
+ return child.clock().Add(time.Minute)
}
-
- test.messages, err = openDaemonMessages(test.loops)
- require.NoError(t, err)
-
- return test
+ require.ErrorContains(t, child.schedules.deliver(t.Context(), test.loops[0]), "does not match")
+ require.NoError(t, child.schedules.deliver(t.Context(), test.loops[1]))
+ test.Start(t, 0, 1, 2)
+ test.events[1].WaitFor(t, "idle", 1)
+ require.Equal(t, 1, server.Calls())
+ require.Equal(t, child.agent.CacheKey(), server.Call(0).body["prompt_cache_key"])
+ require.Zero(t, test.agents[0].memory.Len())
+ require.Zero(t, test.agents[2].memory.Len())
+ require.Contains(t, test.events[1].LastEvent("work").Text, "Check the plants.")
+ require.Equal(t, scheduleSource, test.events[1].LastEvent("work").Source)
}