]> git.giacomolli.uk - onechat/commitdiff
Separate multiline Go statements
authorAnderson A. Giacomolli <redacted>
Wed, 11 Nov 2026 13:55:28 +0000 (10:55 -0300)
committerAnderson A. Giacomolli <redacted>
Wed, 11 Nov 2026 13:55:28 +0000 (10:55 -0300)
Multiline literals and calls have a blank separator before the next
logical block, except for their immediate error check. This includes
test case literals before loops. Event fields and empty callbacks use
multiline bodies. Whitespace and literal layout do not change behavior.

Issue: 3cdc678

48 files changed:
agent_test.go
backup.go
backup_archive.go
backup_archive_test.go
backup_test.go
compactor.go
daemon.go
daemon_agent_test.go
daemon_children_test.go
daemon_message.go
daemon_message_helper_test.go
daemon_message_test.go
image_model.go
image_model_test.go
image_show_test.go
image_test.go
journal.go
memory.go
memory_test.go
model_test.go
prompt.go
prompt_test.go
routes.go
routes_test.go
schedule_test.go
subagent.go
subagent_run_test.go
syncer.go
telegram_api_test.go
telegram_fake_helper_test.go
telegram_image_test.go
telegram_test.go
telegram_voice_test.go
terminal_image_test.go
terminal_markdown_test.go
terminal_test.go
tools.go
tools_test.go
tree_test.go
turn_test.go
voice_transcriber.go
voice_transcriber_test.go
whatsapp.go
whatsapp_import_test.go
whatsapp_send_test.go
whatsapp_state.go
whatsapp_test.go
whatsapp_voice_test.go

index 8b6775f957cc2088c796b2140e48e58c7f2b099d..5b48c220c60d5f0e462529e4200491666cd530d9 100644 (file)
@@ -170,6 +170,7 @@ func TestChildConfigNeedsAParentAndAnArea(t *testing.T) {
                        responsibility: "Keep the garden plans.",
                },
        }
                        responsibility: "Keep the garden plans.",
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        dir := t.TempDir()
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        dir := t.TempDir()
index 56cf516ba53009a84f6ea69b7e1b14e370e7dbd1..9398772ce8e0839fde894b2ce79320c4e3fc3739 100644 (file)
--- a/backup.go
+++ b/backup.go
@@ -39,6 +39,7 @@ func backup(ctx context.Context, path string, out io.Writer) (err error) {
        stop := context.AfterFunc(ctx, func() {
                _ = conn.Close()
        })
        stop := context.AfterFunc(ctx, func() {
                _ = conn.Close()
        })
+
        defer stop()
 
        err = json.NewEncoder(conn).Encode(SocketRequest{
        defer stop()
 
        err = json.NewEncoder(conn).Encode(SocketRequest{
index 35a389cbcde7856ac14d41d02eb90311628c5019..3cd6013db9613b2b2ada9874ea2f0374e7043357 100644 (file)
@@ -101,6 +101,7 @@ func backupArchive(ctx context.Context, root, path string) (size int64, err erro
 
                return nil
        })
 
                return nil
        })
+
        err = errors.Join(err, archive.Close(), compressed.Close())
        if err != nil {
                return 0, fmt.Errorf("write archive: %w", err)
        err = errors.Join(err, archive.Close(), compressed.Close())
        if err != nil {
                return 0, fmt.Errorf("write archive: %w", err)
@@ -150,6 +151,7 @@ func backupExtract(ctx context.Context, path, root string) (err error) {
                ctx:    ctx,
                reader: compressed,
        })
                ctx:    ctx,
                reader: compressed,
        })
+
        seen := make(map[string]bool)
 
        var directories []*tar.Header
        seen := make(map[string]bool)
 
        var directories []*tar.Header
index e42733395834d26d6ac44fe2ce2672e004dfd639..dff09f8533e224249c87b48b17295776d706b0c8 100644 (file)
@@ -111,6 +111,7 @@ func TestRestoreRejectsUnsafeArchiveEntries(t *testing.T) {
                        },
                },
        }
                        },
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        archive := backupTestArchive(t, tc.header)
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        archive := backupTestArchive(t, tc.header)
index 8628ee4a5858cadd4c8118018113694ae2d0c1c4..2fb218a584770b9e6fe3ac59e9dab340c1713265 100644 (file)
@@ -26,6 +26,7 @@ func TestRunningDaemonBackupRestoresTheChatAndLinks(t *testing.T) {
                "agent/instructions.md":      "Keep the instructions.\n",
                "agent/telegram-offset.json": "23\n",
        }
                "agent/instructions.md":      "Keep the instructions.\n",
                "agent/telegram-offset.json": "23\n",
        }
+
        for name, data := range files {
                path := filepath.Join(dir, name)
                require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700))
        for name, data := range files {
                path := filepath.Join(dir, name)
                require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700))
@@ -236,6 +237,7 @@ func TestUnsafeRestoreInstallsNoArchiveFiles(t *testing.T) {
                        Mode:     0o600,
                },
        )
                        Mode:     0o600,
                },
        )
+
        dir := t.TempDir()
        err := backupRestore(t.Context(), archive, dir)
        require.ErrorContains(t, err, "unsafe archive path")
        dir := t.TempDir()
        err := backupRestore(t.Context(), archive, dir)
        require.ErrorContains(t, err, "unsafe archive path")
@@ -337,6 +339,7 @@ func TestSessionBackupWaitsForRemovalToFinish(t *testing.T) {
        require.Never(t, func() bool {
                return len(backedUp) != 0
        }, 50*time.Millisecond, time.Millisecond)
        require.Never(t, func() bool {
                return len(backedUp) != 0
        }, 50*time.Millisecond, time.Millisecond)
+
        release.Do(func() {
                close(disconnect.release)
        })
        release.Do(func() {
                close(disconnect.release)
        })
index e1014288abb988a88a9a091e5e4261400438dc44..6738955143a59125c77f01dd73a938eb0a3dd203 100644 (file)
@@ -125,6 +125,7 @@ func compactorTask(task Task) (string, error) {
 <input>
 %s
 </input>`, task.Index, nodeBytes, compactorRuler, task.Input[0])
 <input>
 %s
 </input>`, task.Index, nodeBytes, compactorRuler, task.Input[0])
+
                if len(task.Images) > 0 {
                        text += "\nKeep each [image FILE] name and describe its kind, visible text, amounts, names, and context."
                }
                if len(task.Images) > 0 {
                        text += "\nKeep each [image FILE] name and describe its kind, visible text, amounts, names, and context."
                }
index dd75d316316a4195ef2b79b4fc42b3c4b27e6bcf..9f71d801be1c985ee70590e7118e69eeba91abbb 100644 (file)
--- a/daemon.go
+++ b/daemon.go
@@ -387,6 +387,7 @@ func daemonServe(ctx context.Context, listener net.Listener, bot *TelegramBot, w
                root:     l,
                messages: messages,
        }
                root:     l,
                messages: messages,
        }
+
        l.tools.children = runtime
 
        var workers sync.WaitGroup
        l.tools.children = runtime
 
        var workers sync.WaitGroup
index 3fceb34ecea885f349b7bbf2e8690faafea95a22..5ee342a04018939c4b6d01ed1ea878038abbc3f2 100644 (file)
@@ -26,6 +26,7 @@ func TestDaemonOpensAndRunsTheRootAndTwoChildren(t *testing.T) {
        t.Cleanup(func() {
                require.NoError(t, agents.Close())
        })
        t.Cleanup(func() {
                require.NoError(t, agents.Close())
        })
+
        require.Len(t, agents, 3)
        require.Equal(t, []string{"Garden", "Library"}, agents.ChildNames())
 
        require.Len(t, agents, 3)
        require.Equal(t, []string{"Garden", "Library"}, agents.ChildNames())
 
@@ -253,6 +254,7 @@ func TestInvalidChildRelationshipsStopStartup(t *testing.T) {
                        cause: "must have its own name and ID",
                },
        }
                        cause: "must have its own name and ID",
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        dir := t.TempDir()
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        dir := t.TempDir()
index b9e212f8646646473ee08512d4c54593c7e89632..b377b4d348dff0a6ae946de93954eef9eae901bf 100644 (file)
@@ -49,6 +49,7 @@ func TestDaemonToolsCreateAndTellAChildDuringATurn(t *testing.T) {
 
                modelWriteEvents(w, modelCompletedEvent(items, 100, 0, 5))
        }
 
                modelWriteEvents(w, modelCompletedEvent(items, 100, 0, 5))
        }
+
        whatsapp, _, _ := whatsappStartTest(t, dir, newTestClock())
        listener, err := daemonListenSocket(filepath.Join(dir, socketFileName))
        require.NoError(t, err)
        whatsapp, _, _ := whatsappStartTest(t, dir, newTestClock())
        listener, err := daemonListenSocket(filepath.Join(dir, socketFileName))
        require.NoError(t, err)
@@ -111,6 +112,7 @@ func TestChildCreationRefusesUsedNames(t *testing.T) {
        test.root.runs.subagents.draw = func() string {
                return "BlueFox"
        }
        test.root.runs.subagents.draw = func() string {
                return "BlueFox"
        }
+
        subagent, err := test.root.runs.subagents.Create(t.Context())
        require.NoError(t, err)
        require.Equal(t, "BlueFox", subagent.name)
        subagent, err := test.root.runs.subagents.Create(t.Context())
        require.NoError(t, err)
        require.Equal(t, "BlueFox", subagent.name)
@@ -145,6 +147,7 @@ func TestChildCreationAcceptsAreaNames(t *testing.T) {
                        child: "A" + strings.Repeat("b", 31),
                },
        }
                        child: "A" + strings.Repeat("b", 31),
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        test := newDaemonChildrenTest(t)
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        test := newDaemonChildrenTest(t)
@@ -209,6 +212,7 @@ func TestChildCreationRefusesInvalidAreaNames(t *testing.T) {
                        child: "Hous\u00e9",
                },
        }
                        child: "Hous\u00e9",
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        test := newDaemonChildrenTest(t)
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        test := newDaemonChildrenTest(t)
@@ -320,6 +324,7 @@ func TestChildNamesStayOutOfSubagentDraws(t *testing.T) {
 
                return name
        }
 
                return name
        }
+
        subagent, err := test.root.runs.subagents.Create(t.Context())
        require.NoError(t, err)
        require.Equal(t, "BlueFox", subagent.name)
        subagent, err := test.root.runs.subagents.Create(t.Context())
        require.NoError(t, err)
        require.Equal(t, "BlueFox", subagent.name)
@@ -364,6 +369,7 @@ func TestZoomReadsTheChildViewAndKeepsSubagentLogs(t *testing.T) {
        test.root.runs.subagents.draw = func() string {
                return "BlueFox"
        }
        test.root.runs.subagents.draw = func() string {
                return "BlueFox"
        }
+
        subagent, err := test.root.runs.subagents.Create(t.Context())
        require.NoError(t, err)
 
        subagent, err := test.root.runs.subagents.Create(t.Context())
        require.NoError(t, err)
 
@@ -439,6 +445,7 @@ func newDaemonChildrenTest(t *testing.T) *DaemonChildrenTest {
        test := &DaemonChildrenTest{
                server: newModelFake(t),
        }
        test := &DaemonChildrenTest{
                server: newModelFake(t),
        }
+
        agents, err := openDaemonAgents(t.Context(), t.TempDir(), test.server.URL, ModelAPIKey(modelTestKey), defaultConfig().Compactor, time.Now, discardLog())
        require.NoError(t, err)
 
        agents, err := openDaemonAgents(t.Context(), t.TempDir(), test.server.URL, ModelAPIKey(modelTestKey), defaultConfig().Compactor, time.Now, discardLog())
        require.NoError(t, err)
 
@@ -457,6 +464,7 @@ func newDaemonChildrenTest(t *testing.T) *DaemonChildrenTest {
                root:     test.root,
                messages: messages,
        }
                root:     test.root,
                messages: messages,
        }
+
        test.root.tools.children = test.runtime
        t.Cleanup(func() {
                cancel()
        test.root.tools.children = test.runtime
        t.Cleanup(func() {
                cancel()
index b70947d7a0e934e3b44fa15b01d7574b7afabb05..eade7274d50275a0dd5f3156437b870ae8544167 100644 (file)
@@ -65,6 +65,7 @@ func openDaemonMessages(loops []*TurnLoop) (*DaemonMessages, error) {
                ordered: append([]*TurnLoop(nil), loops...),
                loops:   make(map[string]*TurnLoop),
        }
                ordered: append([]*TurnLoop(nil), loops...),
                loops:   make(map[string]*TurnLoop),
        }
+
        for _, loop := range loops {
                m.loops[loop.agent.config.Name] = loop
                loop.messages = m
        for _, loop := range loops {
                m.loops[loop.agent.config.Name] = loop
                loop.messages = m
@@ -173,6 +174,7 @@ func (m *DaemonMessages) deliver(message *DaemonMessage) error {
                Source: source,
                Status: status,
        })
                Source: source,
                Status: status,
        })
+
        loop.Wake(logged)
 
        if message.WhatsAppBatch > 0 {
        loop.Wake(logged)
 
        if message.WhatsAppBatch > 0 {
@@ -370,6 +372,7 @@ func (m *DaemonMessages) PruneWhatsApp(batches []WhatsAppBatch) error {
        m.state.WhatsAppBatches = slices.DeleteFunc(m.state.WhatsAppBatches, func(number int) bool {
                return !held[number]
        })
        m.state.WhatsAppBatches = slices.DeleteFunc(m.state.WhatsAppBatches, func(number int) bool {
                return !held[number]
        })
+
        if len(m.state.WhatsAppBatches) == before {
                return nil
        }
        if len(m.state.WhatsAppBatches) == before {
                return nil
        }
index 2a627e2730aba4beef10c51a2522cc5db40346b3..4ac80d310816f366bc388f4cffe15ed6fbfc21de 100644 (file)
@@ -36,6 +36,7 @@ func newDaemonMessageTest(t *testing.T, dir string, server *ModelFake) *DaemonMe
        test := &DaemonMessageTest{
                logs: &SyncBuffer{},
        }
        test := &DaemonMessageTest{
                logs: &SyncBuffer{},
        }
+
        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.agents, err = openDaemonAgents(t.Context(), dir, server.URL, ModelAPIKey(modelTestKey), defaultConfig().Compactor, newTestClock().Now, daemonTestLogger(test.logs))
        require.NoError(t, err)
        t.Cleanup(func() {
index 432a6406f2fa9882aea2279898edea74c373f4b2..27138d3f895fa57fb24bbe666fda7efecc2daa47 100644 (file)
@@ -24,6 +24,7 @@ func TestParentMessageWakesChildAndFinalReplyWakesParent(t *testing.T) {
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
        }
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
        }
+
        test := newDaemonMessageTest(t, t.TempDir(), server)
        test.Start(t, 0, 1, 2)
        require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the gate size."))
        test := newDaemonMessageTest(t, t.TempDir(), server)
        test.Start(t, 0, 1, 2)
        require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the gate size."))
@@ -68,6 +69,7 @@ func TestChildReportSendsATelegramCompletionBeforeTheParentReply(t *testing.T) {
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
        }
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
        }
+
        test := newDaemonMessageTest(t, t.TempDir(), server)
        telegram := newTelegramFake(t)
        bot := newTelegramBot(newTelegramAPI(telegram.URL, telegramTestToken), telegramTestOwner, filepath.Join(t.TempDir(), telegramOffsetFileName), discardLog())
        test := newDaemonMessageTest(t, t.TempDir(), server)
        telegram := newTelegramFake(t)
        bot := newTelegramBot(newTelegramAPI(telegram.URL, telegramTestToken), telegramTestOwner, filepath.Join(t.TempDir(), telegramOffsetFileName), discardLog())
@@ -97,6 +99,7 @@ func TestChildReportSendsATelegramCompletionBeforeTheParentReply(t *testing.T) {
        require.Eventually(t, func() bool {
                return len(telegram.Sent()) >= 2
        }, 10*time.Second, time.Millisecond)
        require.Eventually(t, func() bool {
                return len(telegram.Sent()) >= 2
        }, 10*time.Second, time.Millisecond)
+
        require.Equal(t, []string{"[Garden] finished", "I will ask the user."}, telegram.Sent())
        require.Equal(t, subagentFinished, test.events[0].LastEvent("work").Status)
        require.Equal(t, "[Garden] Approve the gate size.", test.events[0].Last("work"))
        require.Equal(t, []string{"[Garden] finished", "I will ask the user."}, telegram.Sent())
        require.Equal(t, subagentFinished, test.events[0].LastEvent("work").Status)
        require.Equal(t, "[Garden] Approve the gate size.", test.events[0].Last("work"))
@@ -130,12 +133,14 @@ func TestEmptyChildReplySendsNothingAndWakesNobody(t *testing.T) {
                        text: " \n\t ",
                },
        }
                        text: " \n\t ",
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        server := newModelFake(t)
                        server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
                                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(tc.text)}, 100, 0, 5))
                        }
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        server := newModelFake(t)
                        server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
                                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(tc.text)}, 100, 0, 5))
                        }
+
                        test := newDaemonMessageTest(t, t.TempDir(), server)
                        test.Start(t, 0, 1, 2)
                        require.NoError(t, test.messages.Tell("OneChat", "Garden", "Keep the plan."))
                        test := newDaemonMessageTest(t, t.TempDir(), server)
                        test.Start(t, 0, 1, 2)
                        require.NoError(t, test.messages.Tell("OneChat", "Garden", "Keep the plan."))
@@ -162,6 +167,7 @@ func TestBusyChildQueuesTheNextParentMessage(t *testing.T) {
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
        }
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
        }
+
        test := newDaemonMessageTest(t, t.TempDir(), server)
        test.Start(t, 0, 1)
        require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the first plan."))
        test := newDaemonMessageTest(t, t.TempDir(), server)
        test.Start(t, 0, 1)
        require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the first plan."))
@@ -194,6 +200,7 @@ func TestBusyParentQueuesItsChildReport(t *testing.T) {
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
        }
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
        }
+
        test := newDaemonMessageTest(t, t.TempDir(), server)
        test.Start(t, 0, 1)
 
        test := newDaemonMessageTest(t, t.TempDir(), server)
        test.Start(t, 0, 1)
 
@@ -232,6 +239,7 @@ func TestChildReportsOnlyTheFinalReplyAndTakesQueuedToolInput(t *testing.T) {
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("The plan needs approval.")}, 100, 0, 5))
        }
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("The plan needs approval.")}, 100, 0, 5))
        }
+
        test := newDaemonMessageTest(t, t.TempDir(), server)
        test.Start(t, 1)
        require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the plan."))
        test := newDaemonMessageTest(t, t.TempDir(), server)
        test.Start(t, 1)
        require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the plan."))
@@ -259,6 +267,7 @@ func TestQueuedParentMessageSurvivesDaemonRestart(t *testing.T) {
        server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
        }
        server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
        }
+
        first := newDaemonMessageTest(t, dir, server)
        require.NoError(t, first.messages.Tell("OneChat", "Garden", "Remember the garden plan."))
        require.Equal(t, 1, first.agents[1].memory.Len())
        first := newDaemonMessageTest(t, dir, server)
        require.NoError(t, first.messages.Tell("OneChat", "Garden", "Remember the garden plan."))
        require.Equal(t, 1, first.agents[1].memory.Len())
@@ -319,6 +328,7 @@ func TestInterruptedChildDeliveryResumesAfterDaemonRestart(t *testing.T) {
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
        }
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
        }
+
        first := newDaemonMessageTest(t, dir, server)
        first.Start(t, 1)
        require.NoError(t, first.messages.Tell("OneChat", "Garden", "Finish the garden plan."))
        first := newDaemonMessageTest(t, dir, server)
        first.Start(t, 1)
        require.NoError(t, first.messages.Tell("OneChat", "Garden", "Finish the garden plan."))
@@ -348,6 +358,7 @@ func TestInterruptedParentReportResumesAfterDaemonRestart(t *testing.T) {
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("The plan is ready.")}, 100, 0, 5))
        }
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("The plan is ready.")}, 100, 0, 5))
        }
+
        first := newDaemonMessageTest(t, dir, server)
        first.Start(t, 0, 1)
        require.NoError(t, first.messages.Tell("OneChat", "Garden", "Review the plan."))
        first := newDaemonMessageTest(t, dir, server)
        first.Start(t, 0, 1)
        require.NoError(t, first.messages.Tell("OneChat", "Garden", "Review the plan."))
@@ -419,6 +430,7 @@ func TestAgentMessageSnapshotBlocksDelivery(t *testing.T) {
        require.Never(t, func() bool {
                return len(sent) > 0
        }, 50*time.Millisecond, time.Millisecond)
        require.Never(t, func() bool {
                return len(sent) > 0
        }, 50*time.Millisecond, time.Millisecond)
+
        once.Do(func() {
                close(release)
        })
        once.Do(func() {
                close(release)
        })
@@ -463,6 +475,7 @@ func TestParentCanMessageOnlyItsOwnChildren(t *testing.T) {
                        target: "Other",
                },
        }
                        target: "Other",
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        require.ErrorContains(t, test.messages.Tell(tc.source, tc.target, "Do not deliver."), "not a child")
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        require.ErrorContains(t, test.messages.Tell(tc.source, tc.target, "Do not deliver."), "not a child")
@@ -497,6 +510,7 @@ func TestInvalidAgentMessageQueueStopsStartup(t *testing.T) {
                        file: `{"next":0,"pending":[{"id":1,"source":"OneChat","target":"Garden","kind":"user"}]}`,
                },
        }
                        file: `{"next":0,"pending":[{"id":1,"source":"OneChat","target":"Garden","kind":"user"}]}`,
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        dir := t.TempDir()
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        dir := t.TempDir()
@@ -537,6 +551,7 @@ func TestChildScheduleWakesOnlyItsOwner(t *testing.T) {
        server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
        }
        server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
        }
+
        test := newDaemonMessageTest(t, t.TempDir(), server)
        child := test.agents[1]
        at := child.clock().Add(time.Minute).Format(scheduleAtLayout)
        test := newDaemonMessageTest(t, t.TempDir(), server)
        child := test.agents[1]
        at := child.clock().Add(time.Minute).Format(scheduleAtLayout)
@@ -580,12 +595,14 @@ func TestRoutedWhatsAppBatchesWakeOnlyTheirChild(t *testing.T) {
                        },
                },
        }
                        },
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        server := newModelFake(t)
                        server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
                                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
                        }
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        server := newModelFake(t)
                        server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
                                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
                        }
+
                        dir := t.TempDir()
                        test := newDaemonMessageTest(t, dir, server)
                        inbox := whatsappRouteTestInbox(t, dir)
                        dir := t.TempDir()
                        test := newDaemonMessageTest(t, dir, server)
                        inbox := whatsappRouteTestInbox(t, dir)
@@ -621,6 +638,7 @@ func TestWhatsAppUnrouteRestoresRootDelivery(t *testing.T) {
                ID:   whatsappTestAna.String(),
                Name: "Ana",
        }
                ID:   whatsappTestAna.String(),
                Name: "Ana",
        }
+
        source := "whatsapp:" + chat.ID
        require.NoError(t, test.messages.routes.Set(source, "Garden"))
        whatsappRouteTestAdd(t, inbox, chat, "Keep the gate plan.")
        source := "whatsapp:" + chat.ID
        require.NoError(t, test.messages.routes.Set(source, "Garden"))
        whatsappRouteTestAdd(t, inbox, chat, "Keep the gate plan.")
@@ -672,6 +690,7 @@ func TestRoutedWhatsAppRestartDoesNotDeliverTheBatchTwice(t *testing.T) {
                        finish: true,
                },
        }
                        finish: true,
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        dir := t.TempDir()
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        dir := t.TempDir()
@@ -679,12 +698,14 @@ func TestRoutedWhatsAppRestartDoesNotDeliverTheBatchTwice(t *testing.T) {
                        server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
                                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
                        }
                        server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
                                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
                        }
+
                        first := newDaemonMessageTest(t, dir, server)
                        inbox := whatsappRouteTestInbox(t, dir)
                        chat := WhatsAppChat{
                                ID:   whatsappTestAna.String(),
                                Name: "Ana",
                        }
                        first := newDaemonMessageTest(t, dir, server)
                        inbox := whatsappRouteTestInbox(t, dir)
                        chat := WhatsAppChat{
                                ID:   whatsappTestAna.String(),
                                Name: "Ana",
                        }
+
                        source := "whatsapp:" + chat.ID
                        require.NoError(t, first.messages.routes.Set(source, "Garden"))
                        whatsappRouteTestAdd(t, inbox, chat, "Remember the gate plan.")
                        source := "whatsapp:" + chat.ID
                        require.NoError(t, first.messages.routes.Set(source, "Garden"))
                        whatsappRouteTestAdd(t, inbox, chat, "Remember the gate plan.")
@@ -747,6 +768,7 @@ func TestQueuedWhatsAppBatchRecoversBeforeItsLogAppend(t *testing.T) {
                        id:   new(0),
                },
        }
                        id:   new(0),
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        dir := t.TempDir()
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        dir := t.TempDir()
@@ -768,6 +790,7 @@ func TestQueuedWhatsAppBatchRecoversBeforeItsLogAppend(t *testing.T) {
                                        WhatsAppBatch: 1,
                                }},
                        }
                                        WhatsAppBatch: 1,
                                }},
                        }
+
                        data, err := json.Marshal(file)
                        require.NoError(t, err)
                        require.NoError(t, os.WriteFile(path, data, 0o600))
                        data, err := json.Marshal(file)
                        require.NoError(t, err)
                        require.NoError(t, os.WriteFile(path, data, 0o600))
@@ -824,6 +847,7 @@ func TestRoutedWhatsAppBatchesCanCloseOutOfNumberOrder(t *testing.T) {
                ID:   whatsappTestAna.String(),
                Name: "Ana",
        }
                ID:   whatsappTestAna.String(),
                Name: "Ana",
        }
+
        second := WhatsAppChat{
                ID:    whatsappTestFamily.String(),
                Name:  "Garden plans",
        second := WhatsAppChat{
                ID:    whatsappTestFamily.String(),
                Name:  "Garden plans",
@@ -867,10 +891,12 @@ func TestWhatsAppCatchupSeparatesChildAndRootBatches(t *testing.T) {
                Name:  "Garden plans",
                Group: true,
        }
                Name:  "Garden plans",
                Group: true,
        }
+
        library := WhatsAppChat{
                ID:   whatsappTestBruno.String(),
                Name: "Bruno",
        }
        library := WhatsAppChat{
                ID:   whatsappTestBruno.String(),
                Name: "Bruno",
        }
+
        root := WhatsAppChat{
                ID:   whatsappTestAna.String(),
                Name: "Ana",
        root := WhatsAppChat{
                ID:   whatsappTestAna.String(),
                Name: "Ana",
index 696514294abc8889ff70a1fd8e77f3e903e315a0..71f51aaf5b0b588cdff0fbddea7e94d947a1a130 100644 (file)
@@ -29,6 +29,7 @@ func imageParts(store *ImageStore, text string, names []string) ([]ModelInputPar
                Type: "input_text",
                Text: text,
        }}
                Type: "input_text",
                Text: text,
        }}
+
        for _, name := range names {
                if store == nil {
                        return nil, errors.New("image store is not available")
        for _, name := range names {
                if store == nil {
                        return nil, errors.New("image store is not available")
index 53b0f313961782beeca5117e93a5dd14e3924fa3..04f2b4b65d5ca1eb17d9410912ed491526886461 100644 (file)
@@ -105,6 +105,7 @@ func TestLeafCompactionDescribesImagesOnceAndMergesOnlyText(t *testing.T) {
                Input:   []string{"user: " + imageText([]string{name}, "Read the receipt.")},
                Images:  []string{name},
        }
                Input:   []string{"user: " + imageText([]string{name}, "Read the receipt.")},
                Images:  []string{name},
        }
+
        text, err := compactor.Summarize(t.Context(), task)
        require.NoError(t, err)
        require.Equal(t, "receipt, Ada, $12, red square", text)
        text, err := compactor.Summarize(t.Context(), task)
        require.NoError(t, err)
        require.Equal(t, "receipt, Ada, $12, red square", text)
index 8df642af4111008b4b9d7cb2032e6af44c20ff75..9317cbb896ad4d29126c49c6db69e760f4f2bae8 100644 (file)
@@ -22,6 +22,7 @@ func TestImageShowUsesStoredImageForTelegramAndTerminal(t *testing.T) {
                store: store,
                bot:   bot,
        }}
                store: store,
                bot:   bot,
        }}
+
        args, err := json.Marshal(ToolImageShowArguments{
                Image:   name,
                Caption: "A red point.",
        args, err := json.Marshal(ToolImageShowArguments{
                Image:   name,
                Caption: "A red point.",
@@ -79,6 +80,7 @@ func TestImageShowFallsBackOnlyWhenTelegramRefusesPhoto(t *testing.T) {
                        failure: true,
                },
        }
                        failure: true,
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        telegram := newTelegramFake(t)
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        telegram := newTelegramFake(t)
@@ -89,6 +91,7 @@ func TestImageShowFallsBackOnlyWhenTelegramRefusesPhoto(t *testing.T) {
 
                                return 0, ""
                        }
 
                                return 0, ""
                        }
+
                        store := &ImageStore{dir: t.TempDir()}
                        picture := imageTestPNG(t, 3, 3)
                        name, err := store.Save(picture)
                        store := &ImageStore{dir: t.TempDir()}
                        picture := imageTestPNG(t, 3, 3)
                        name, err := store.Save(picture)
@@ -99,6 +102,7 @@ func TestImageShowFallsBackOnlyWhenTelegramRefusesPhoto(t *testing.T) {
                                store: store,
                                bot:   bot,
                        }
                                store: store,
                                bot:   bot,
                        }
+
                        _, err = show.Show(t.Context(), name, "Caption")
                        if tc.failure {
                                require.Error(t, err)
                        _, err = show.Show(t.Context(), name, "Caption")
                        if tc.failure {
                                require.Error(t, err)
@@ -194,6 +198,7 @@ func TestAgentShowsTelegramReceivedImageToItsOwner(t *testing.T) {
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("Shown.")}, 100, 0, 5))
        }
 
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("Shown.")}, 100, 0, 5))
        }
+
        socket := daemonStartTestWithBot(t, server, bot)
        terminal := terminalStartTestClient(t, socket)
        terminal.WaitFor(t, "[connected to OneChat]")
        socket := daemonStartTestWithBot(t, server, bot)
        terminal := terminalStartTestClient(t, socket)
        terminal.WaitFor(t, "[connected to OneChat]")
@@ -205,10 +210,12 @@ func TestAgentShowsTelegramReceivedImageToItsOwner(t *testing.T) {
                Width:  6,
                Height: 4,
        }}
                Width:  6,
                Height: 4,
        }}
+
        telegram.Add(TelegramUpdate{
                UpdateID: 1,
                Message:  message,
        })
        telegram.Add(TelegramUpdate{
                UpdateID: 1,
                Message:  message,
        })
+
        terminal.WaitFor(t, "Shown.\n[done]")
        require.Contains(t, terminal.Output(), "[result] [image "+name+"]")
        require.Contains(t, terminal.Output(), filepath.Join(imageDirName, name))
        terminal.WaitFor(t, "Shown.\n[done]")
        require.Contains(t, terminal.Output(), "[result] [image "+name+"]")
        require.Contains(t, terminal.Output(), filepath.Join(imageDirName, name))
index 24007d70acb366d03064c172b8264cce2fbe3673..d92410e95a00041108839030d0007170294eb18a 100644 (file)
@@ -20,6 +20,7 @@ func TestImagesArePrivateSharedAndWrittenOnce(t *testing.T) {
                dir:    filepath.Join(root.dir, "children", "House"),
                config: AgentConfig{Parent: root.config.Name},
        }
                dir:    filepath.Join(root.dir, "children", "House"),
                config: AgentConfig{Parent: root.config.Name},
        }
+
        require.Equal(t, root.Images().dir, child.Images().dir)
 
        data := imageTestPNG(t, 3, 2)
        require.Equal(t, root.Images().dir, child.Images().dir)
 
        data := imageTestPNG(t, 3, 2)
index bb1897c68b22f8dce8acc51d34575d6cdc5b6eeb..3fb8f649d7c6cdacfe89d40102f733c3a423433d 100644 (file)
@@ -71,6 +71,7 @@ func (j *Journal) Write(day string, line []byte) (Position, error) {
                offset: j.size,
                length: uint32(len(line)),
        }
                offset: j.size,
                length: uint32(len(line)),
        }
+
        j.size += uint64(len(line))
 
        return place, nil
        j.size += uint64(len(line))
 
        return place, nil
index df99a9524ccc9f6e81725582946582781f1545c7..5ea9007972b63a3448af75940d1246af9d54908f 100644 (file)
--- a/memory.go
+++ b/memory.go
@@ -659,6 +659,7 @@ func (m *Memory) run(key NodeKey, task Task) {
                        Text:  text,
                        Size:  len(text),
                })
                        Text:  text,
                        Size:  len(text),
                })
+
                m.syncer.Written()
        }
 
                m.syncer.Written()
        }
 
index 6713f6dab219d83d0139ec8cc3656bc19f0cba42..f317b0af555cf1ddc5b80d04d11e27f513a1dcaf 100644 (file)
@@ -143,6 +143,7 @@ func TestMemoryRunsAtMostEightBuilds(t *testing.T) {
        slices.SortFunc(running, func(a, b NodeKey) int {
                return a.index - b.index
        })
        slices.SortFunc(running, func(a, b NodeKey) int {
                return a.index - b.index
        })
+
        require.Equal(t, []NodeKey{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}}, running)
 
        require.Eventually(t, func() bool {
        require.Equal(t, []NodeKey{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}}, running)
 
        require.Eventually(t, func() bool {
@@ -497,6 +498,7 @@ func TestQueuedAppendRefusesAnInvalidReceipt(t *testing.T) {
                        cause: "read queued message",
                },
        }
                        cause: "read queued message",
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        _, err := memory.AppendOnce("user", "Other text.", &tc.id, func(int) error {
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        _, err := memory.AppendOnce("user", "Other text.", &tc.id, func(int) error {
index 90d0e93366d7226bf37e0bb795f48fbcd08ba749..3ea82d4c20c0f7a8d83fc28a2dbc886c92bccbd1 100644 (file)
@@ -50,6 +50,7 @@ func TestClientSendsTheFixedRequestFields(t *testing.T) {
                        map[string]any{"type": "input_text", "text": "hello"},
                },
        }}, body["input"])
                        map[string]any{"type": "input_text", "text": "hello"},
                },
        }}, body["input"])
+
        require.Len(t, body["tools"], 1)
 }
 
        require.Len(t, body["tools"], 1)
 }
 
index 29e58eef4f45070228965ff734b66e3406f62bda..0571ecfc9cc103ec40a384b8e885f923faaab579 100644 (file)
--- a/prompt.go
+++ b/prompt.go
@@ -223,6 +223,7 @@ message outside your area. Your final reply is your report to the parent.
 End your turn with an empty reply when nothing needs the parent or the user.
 Send nothing to WhatsApp. Draft replies in your report instead, so the parent
 can ask the user before sending.`, 1)
 End your turn with an empty reply when nothing needs the parent or the user.
 Send nothing to WhatsApp. Draft replies in your report instead, so the parent
 can ask the user before sending.`, 1)
+
        text = strings.Replace(text, promptChildrenText, "", 1)
 
        return promptWithInstructions(text, "Do the tasks of your area yourself, with your tools.", promptInstructionsClause, instructions)
        text = strings.Replace(text, promptChildrenText, "", 1)
 
        return promptWithInstructions(text, "Do the tasks of your area yourself, with your tools.", promptInstructionsClause, instructions)
index 888057f998e3513bcb62822b0e0348ce48c82bff..158784b9a34d8e0e6d5c2e8198346d4d9d0a8bac 100644 (file)
@@ -65,6 +65,7 @@ func TestPromptEndsWithTheInstructions(t *testing.T) {
        require.Contains(t, text, `Do the user's tasks yourself, with your tools, following the user's instructions
 at the end of this prompt: who they are, how their files are organized and how
 they want work done.`)
        require.Contains(t, text, `Do the user's tasks yourself, with your tools, following the user's instructions
 at the end of this prompt: who they are, how their files are organized and how
 they want work done.`)
+
        require.True(t, strings.HasSuffix(text, "\n\n# The user's instructions\n\nI am Lena. Keep replies short."))
 }
 
        require.True(t, strings.HasSuffix(text, "\n\n# The user's instructions\n\nI am Lena. Keep replies short."))
 }
 
index 02e28e2aac24d1271ad5fc69fb908c02b8c46639..6d72898196d7a4ab0fc5b04e7840ded2e1d9bc27 100644 (file)
--- a/routes.go
+++ b/routes.go
@@ -30,6 +30,7 @@ func openRoutes(path string, logger *slog.Logger) (*Routes, error) {
                logger:  logger,
                targets: make(map[string]string),
        }
                logger:  logger,
                targets: make(map[string]string),
        }
+
        data, err := os.ReadFile(path)
        if errors.Is(err, os.ErrNotExist) {
                return r, nil
        data, err := os.ReadFile(path)
        if errors.Is(err, os.ErrNotExist) {
                return r, nil
index dda77ea4cdfe756940362ed8ae92282b0f39bcc3..2aadc7e7f2925dc444740325aa8070743c8794c2 100644 (file)
@@ -88,6 +88,7 @@ func TestRoutesRefuseUnknownSourcesAndNonchildren(t *testing.T) {
                        cause:  "must be a child",
                },
        }
                        cause:  "must be a child",
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        arguments := `{"source":"` + tc.source + `","agent":"` + tc.agent + `"}`
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        arguments := `{"source":"` + tc.source + `","agent":"` + tc.agent + `"}`
@@ -122,6 +123,7 @@ func TestWhatsAppStatusListsRoutes(t *testing.T) {
        whatsapp.Command("status", func(text string) {
                answer = text
        })
        whatsapp.Command("status", func(text string) {
                answer = text
        })
+
        require.Contains(t, answer, "WhatsApp is not linked.")
        require.Contains(t, answer, "WhatsApp routes:\nwhatsapp:"+whatsappTestFamily.String()+" -> Garden")
 }
        require.Contains(t, answer, "WhatsApp is not linked.")
        require.Contains(t, answer, "WhatsApp routes:\nwhatsapp:"+whatsappTestFamily.String()+" -> Garden")
 }
@@ -144,6 +146,7 @@ func TestInvalidRouteFileStopsStartup(t *testing.T) {
                        text: `{"whatsapp:example@lid":""}`,
                },
        }
                        text: `{"whatsapp:example@lid":""}`,
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        path := filepath.Join(t.TempDir(), "routes.json")
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        path := filepath.Join(t.TempDir(), "routes.json")
index cb839d5f95351d810c38a344319da5f3e8424bd6..03d33c7eb36d91b1d1572ac0436878eca9ef982e 100644 (file)
@@ -287,6 +287,7 @@ func TestSchedulesRunMissedSchedulesOnceAtTheStart(t *testing.T) {
                "[Schedule 1] Sunday 2026-11-08 09:00 -03:00, once\nRemind the user to call.\n" + scheduleLateLine + "\n" + scheduleEmptyReply,
                "[Schedule 2] Sunday 2026-11-08 10:00 -03:00, repeats 0 * * * *\nTell the user the time.\n" + scheduleLateLine + "\n" + scheduleEmptyReply,
        }, texts[len(texts)-2:])
                "[Schedule 1] Sunday 2026-11-08 09:00 -03:00, once\nRemind the user to call.\n" + scheduleLateLine + "\n" + scheduleEmptyReply,
                "[Schedule 2] Sunday 2026-11-08 10:00 -03:00, repeats 0 * * * *\nTell the user the time.\n" + scheduleLateLine + "\n" + scheduleEmptyReply,
        }, texts[len(texts)-2:])
+
        require.Equal(t, "2 | Sunday 2026-11-08 21:00 -03:00 | 0 * * * * | Tell the user the time.", schedules.List())
        require.Equal(t, "<6>schedule ran id=1 late=11h30m0s\n<6>schedule ran id=2 late=10h30m0s\n", logs.String())
 }
        require.Equal(t, "2 | Sunday 2026-11-08 21:00 -03:00 | 0 * * * * | Tell the user the time.", schedules.List())
        require.Equal(t, "<6>schedule ran id=1 late=11h30m0s\n<6>schedule ran id=2 late=10h30m0s\n", logs.String())
 }
index 035f57d51bbced386dc9edea8f1b540c20a06d15..437aeb58d589ef7a545a1cdf1a95e3787bb0a2fa 100644 (file)
@@ -174,6 +174,7 @@ func (s *Subagents) Get(ctx context.Context, name string) (*Subagent, error) {
                log:    log,
                syncer: syncer,
        }
                log:    log,
                syncer: syncer,
        }
+
        s.open[name] = subagent
 
        return subagent, nil
        s.open[name] = subagent
 
        return subagent, nil
index 71c96ea60c0e312eb00971b9e07bc0023f9ac3e9..efd387cccde2270fab18a6459758c3e8d112c446 100644 (file)
@@ -64,6 +64,7 @@ func TestSpawnRunsASubagentInTheBackground(t *testing.T) {
                "work: " + report,
                "reply: Noted.",
        }, test.Log(t))
                "work: " + report,
                "reply: Noted.",
        }, test.Log(t))
+
        require.Equal(t, Event{
                Kind:   "work",
                ID:     4,
        require.Equal(t, Event{
                Kind:   "work",
                ID:     4,
@@ -71,6 +72,7 @@ func TestSpawnRunsASubagentInTheBackground(t *testing.T) {
                Source: name,
                Status: subagentFinished,
        }, test.events.LastEvent("work"))
                Source: name,
                Status: subagentFinished,
        }, test.events.LastEvent("work"))
+
        require.Equal(t, "<6>subagent started name="+name+"\n<6>subagent done name="+name+" after=0s\n", test.logs.String())
 
        var first, second *ModelFakeCall
        require.Equal(t, "<6>subagent started name="+name+"\n<6>subagent done name="+name+" after=0s\n", test.logs.String())
 
        var first, second *ModelFakeCall
@@ -198,6 +200,7 @@ func TestFullContextGivesAFailedReport(t *testing.T) {
                "work: [" + name + "]\nfailed: create model response: model call: HTTP 400: " + subagentTestFullContext + "\n\nFull chat: zoom(\"" + name + "\")",
                "reply: It failed.",
        }, test.Log(t))
                "work: [" + name + "]\nfailed: create model response: model call: HTTP 400: " + subagentTestFullContext + "\n\nFull chat: zoom(\"" + name + "\")",
                "reply: It failed.",
        }, test.Log(t))
+
        require.Equal(t, subagentFailed, test.events.LastEvent("work").Status)
        require.Equal(t, "<6>subagent started name="+name+"\n<3>subagent failed name="+name+" after=0s: create model response: model call: HTTP 400: "+subagentTestFullContext+"\n", test.logs.String())
 }
        require.Equal(t, subagentFailed, test.events.LastEvent("work").Status)
        require.Equal(t, "<6>subagent started name="+name+"\n<3>subagent failed name="+name+" after=0s: create model response: model call: HTTP 400: "+subagentTestFullContext+"\n", test.logs.String())
 }
@@ -385,6 +388,7 @@ func TestStopEndsASubagentAndItsCommand(t *testing.T) {
                "note: Stopped by the user: " + name + ".",
                "work: " + subagentReport(name, []string{"stopped"}),
        }, test.Log(t))
                "note: Stopped by the user: " + name + ".",
                "work: " + subagentReport(name, []string{"stopped"}),
        }, test.Log(t))
+
        require.Equal(t, subagentStopped, test.events.LastEvent("work").Status)
        require.Equal(t, "<6>subagent started name="+name+"\n<6>subagent stopped name="+name+" after=0s\n", test.logs.String())
 
        require.Equal(t, subagentStopped, test.events.LastEvent("work").Status)
        require.Equal(t, "<6>subagent started name="+name+"\n<6>subagent stopped name="+name+" after=0s\n", test.logs.String())
 
@@ -518,6 +522,7 @@ func TestRestartReportsASubagentThatCannotGoOn(t *testing.T) {
                "work: " + subagentReport("BlueFox", []string{"failed: no agent BlueFox"}),
                "reply: a line",
        }, subagentTestChat(t, memory))
                "work: " + subagentReport("BlueFox", []string{"failed: no agent BlueFox"}),
                "reply: a line",
        }, subagentTestChat(t, memory))
+
        require.Equal(t, subagentFailed, events.LastEvent("work").Status)
        require.Equal(t, "<3>subagent lost at restart name=BlueFox: no agent BlueFox\n", logs.String())
 
        require.Equal(t, subagentFailed, events.LastEvent("work").Status)
        require.Equal(t, "<3>subagent lost at restart name=BlueFox: no agent BlueFox\n", logs.String())
 
index 0784393fcd4df9bbfc44e8d99877eaca9f3bac71..196eb97a7da428935c0ce81eb9567c4d0c20d4bf 100644 (file)
--- a/syncer.go
+++ b/syncer.go
@@ -43,6 +43,7 @@ func openSyncer(dir string) (*Syncer, error) {
        s := newSyncer(func() error {
                return unix.Syncfs(int(file.Fd()))
        })
        s := newSyncer(func() error {
                return unix.Syncfs(int(file.Fd()))
        })
+
        s.file = file
 
        return s, nil
        s.file = file
 
        return s, nil
index ca112743c478b64691eaf177c79a96665402e3a4..03d6c63b6b7691e04146c694e676e8c9a3b0ec15 100644 (file)
@@ -234,6 +234,7 @@ func TestBotErrorsDoNotHoldTheToken(t *testing.T) {
                        server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
                                tc.reply(w)
                        }))
                        server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
                                tc.reply(w)
                        }))
+
                        t.Cleanup(server.Close)
 
                        err := newTelegramAPI(server.URL, telegramTestToken).SendMessage(t.Context(), telegramTestOwner, "hi", nil)
                        t.Cleanup(server.Close)
 
                        err := newTelegramAPI(server.URL, telegramTestToken).SendMessage(t.Context(), telegramTestOwner, "hi", nil)
index 6082d7ddac1b87190475bb6bb583692207b0c293..e860d654359b82282387191af0bed35201ca6eec 100644 (file)
@@ -79,6 +79,7 @@ func newTelegramLocalFake(t *testing.T) *TelegramFake {
                added:   make(chan struct{}, 1),
                stopped: make(chan struct{}),
        }
                added:   make(chan struct{}, 1),
                stopped: make(chan struct{}),
        }
+
        t.Cleanup(func() {
                close(f.stopped)
        })
        t.Cleanup(func() {
                close(f.stopped)
        })
index 7f7b7c008ef979089e543b52fa05281f2b19dc84..94a014b9fdfbd6c942277c9a8d67311e9283afcd 100644 (file)
@@ -50,6 +50,7 @@ func TestTelegramSelectsLargestPhotoAtMost1568Pixels(t *testing.T) {
                UpdateID: 1,
                Message:  message,
        }))
                UpdateID: 1,
                Message:  message,
        }))
+
        test.events.WaitFor(t, "idle", 1)
 
        files, err := os.ReadDir(test.agent.Images().dir)
        test.events.WaitFor(t, "idle", 1)
 
        files, err := os.ReadDir(test.agent.Images().dir)
@@ -163,6 +164,7 @@ func TestTelegramImageDocumentsUseContentFormatAndRejectOtherMedia(t *testing.T)
                        failure: "exceeds 20 MB",
                },
        }
                        failure: "exceeds 20 MB",
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        text, err := telegramImageText(t.Context(), api, store, &TelegramMessage{Document: &tc.document})
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        text, err := telegramImageText(t.Context(), api, store, &TelegramMessage{Document: &tc.document})
@@ -193,6 +195,7 @@ func TestTelegramRetriesImageDownloadBeforeAdvancingOffset(t *testing.T) {
 
                return 0, ""
        }
 
                return 0, ""
        }
+
        bot, logs := telegramStartTestBot(t, telegram)
        server := newModelFake(t)
        socket := daemonStartTestWithBot(t, server, bot)
        bot, logs := telegramStartTestBot(t, telegram)
        server := newModelFake(t)
        socket := daemonStartTestWithBot(t, server, bot)
@@ -205,15 +208,18 @@ func TestTelegramRetriesImageDownloadBeforeAdvancingOffset(t *testing.T) {
                Width:  2,
                Height: 2,
        }}
                Width:  2,
                Height: 2,
        }}
+
        telegram.Add(TelegramUpdate{
                UpdateID: 7,
                Message:  message,
        })
        telegram.Add(TelegramUpdate{
                UpdateID: 7,
                Message:  message,
        })
+
        terminal.WaitFor(t, "[done]")
        require.Eventually(t, func() bool {
                offset, err := telegramLoadOffset(bot.offsetPath)
                return err == nil && offset == 8
        }, 10*time.Second, time.Millisecond)
        terminal.WaitFor(t, "[done]")
        require.Eventually(t, func() bool {
                offset, err := telegramLoadOffset(bot.offsetPath)
                return err == nil && offset == 8
        }, 10*time.Second, time.Millisecond)
+
        require.Contains(t, logs.String(), "receive failed")
        require.Contains(t, logs.String(), "receive back")
        require.NotContains(t, logs.String(), telegramTestToken)
        require.Contains(t, logs.String(), "receive failed")
        require.Contains(t, logs.String(), "receive back")
        require.NotContains(t, logs.String(), telegramTestToken)
@@ -245,6 +251,7 @@ func TestTelegramStorageFailureDoesNotAcceptTheImage(t *testing.T) {
                Width:  2,
                Height: 2,
        }}
                Width:  2,
                Height: 2,
        }}
+
        err := bot.handle(t.Context(), test.loop, nil, TelegramUpdate{
                UpdateID: 1,
                Message:  message,
        err := bot.handle(t.Context(), test.loop, nil, TelegramUpdate{
                UpdateID: 1,
                Message:  message,
index dee692d43d3f2f871c11142903f0764b3f171d44..8d033ab3aa62cd33ed408fe4835aef17fb301eb7 100644 (file)
@@ -192,11 +192,13 @@ func TestBotSkipsTheWorkOfWhatsAppAndTheSchedules(t *testing.T) {
                Source: whatsappSource,
                Text:   "[WhatsApp] Ana Souza",
        })
                Source: whatsappSource,
                Text:   "[WhatsApp] Ana Souza",
        })
+
        bot.Notify(Event{
                Kind:   "work",
                Source: scheduleSource,
                Text:   "[Schedule 1] Sunday 2026-11-08 20:05 -03:00, once",
        })
        bot.Notify(Event{
                Kind:   "work",
                Source: scheduleSource,
                Text:   "[Schedule 1] Sunday 2026-11-08 20:05 -03:00, once",
        })
+
        require.Empty(t, bot.queue)
 }
 
        require.Empty(t, bot.queue)
 }
 
@@ -646,6 +648,7 @@ func TestBotLogsNoTokenWhenTheNetworkFails(t *testing.T) {
        require.Eventually(t, func() bool {
                return strings.Contains(logs.String(), "<4>receive failed retry=1s: no answer: ")
        }, 10*time.Second, time.Millisecond)
        require.Eventually(t, func() bool {
                return strings.Contains(logs.String(), "<4>receive failed retry=1s: no answer: ")
        }, 10*time.Second, time.Millisecond)
+
        require.NotContains(t, logs.String(), telegramTestToken)
 }
 
        require.NotContains(t, logs.String(), telegramTestToken)
 }
 
@@ -694,6 +697,7 @@ func TestBotLogsAnOutageOnlyAtItsStartAndEnd(t *testing.T) {
                        "<4>receive failed retry=1s: 502 Bad Gateway",
                        "<6>receive back after=31s",
                }, lines)
                        "<4>receive failed retry=1s: 502 Bad Gateway",
                        "<6>receive back after=31s",
                }, lines)
+
                require.NotContains(t, logs.String(), "secret word")
                require.NotContains(t, logs.String(), strconv.FormatInt(telegramTestOwner, 10))
                cancel()
                require.NotContains(t, logs.String(), "secret word")
                require.NotContains(t, logs.String(), strconv.FormatInt(telegramTestOwner, 10))
                cancel()
index ee5ee3fd35af1d99b5114185596c098ad6b3e603..cd2211c723cd2dbac96e7124edacb1f2d37584d3 100644 (file)
@@ -64,6 +64,7 @@ func TestTelegramVoiceFailuresKeepFollowingTextMoving(t *testing.T) {
                        marker: "transcriber or model is not available",
                },
        }
                        marker: "transcriber or model is not available",
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        test := newTestTurnLoop(t, newModelFake(t))
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        test := newTestTurnLoop(t, newModelFake(t))
index 46a7cfd5182ca3c551f37b29d098a02c4f8362c4..c62d7daf4fffeeecf3edc2e5976de2e561069a1a 100644 (file)
@@ -87,6 +87,7 @@ func TestTerminalImagePathsAndCaptionsHaveNoShellExpansion(t *testing.T) {
                        command: false,
                },
        }
                        command: false,
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        request, command := terminalImageRequest(tc.line)
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        request, command := terminalImageRequest(tc.line)
@@ -138,6 +139,7 @@ func TestTerminalRefusesLargeAndUnsupportedImagesWithoutAModelCall(t *testing.T)
        require.NoError(t, os.WriteFile(path, []byte("not an image"), 0o600))
        require.ErrorContains(t, daemonRunRequest(test.loop, nil, request, func(string) {
        }), "JPEG, PNG, or WebP")
        require.NoError(t, os.WriteFile(path, []byte("not an image"), 0o600))
        require.ErrorContains(t, daemonRunRequest(test.loop, nil, request, func(string) {
        }), "JPEG, PNG, or WebP")
+
        require.Equal(t, 0, test.memory.Len())
        require.Equal(t, 0, server.Calls())
 
        require.Equal(t, 0, test.memory.Len())
        require.Equal(t, 0, server.Calls())
 
index adf0fa483f9fb3f5f30174abeca371d4d047504f..a1676f385b67a25c325cc822778cbb284049cd3d 100644 (file)
@@ -88,6 +88,7 @@ func TestTerminalMarkdownBlocksAndMarks(t *testing.T) {
                        want:    []string{"safe[31m    end"},
                },
        }
                        want:    []string{"safe[31m    end"},
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        require.Equal(t, tc.want, terminalMarkdown(tc.text, tc.columns))
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        require.Equal(t, tc.want, terminalMarkdown(tc.text, tc.columns))
@@ -145,6 +146,7 @@ func TestTerminalMarkdownNarrowLinesKeepQuoteAndListMarks(t *testing.T) {
                        want:    []string{"| a", "| b"},
                },
        }
                        want:    []string{"| a", "| b"},
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        require.Equal(t, tc.want, terminalMarkdown(tc.text, tc.columns))
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        require.Equal(t, tc.want, terminalMarkdown(tc.text, tc.columns))
index 7b68edb4ed389ceb09355c40c02b85d3d62a5376..c93c46f84644e05a39b7ad424b68fecfeba7a1ac 100644 (file)
@@ -96,6 +96,7 @@ func TestTerminalEventsKeepMarkdownInNonterminalOutput(t *testing.T) {
                Kind: "work",
                Text: "[BlueBird]\n" + text,
        })
                Kind: "work",
                Text: "[BlueBird]\n" + text,
        })
+
        require.Equal(t, text+"\n[BlueBird]\n"+text+"\n", out.String())
 }
 
        require.Equal(t, text+"\n[BlueBird]\n"+text+"\n", out.String())
 }
 
@@ -112,6 +113,7 @@ func TestTerminalFileOutputKeepsMarkdown(t *testing.T) {
                Kind: "reply",
                Text: text,
        })
                Kind: "reply",
                Text: text,
        })
+
        require.Zero(t, terminalColumns(out))
 
        contents, err := os.ReadFile(out.Name())
        require.Zero(t, terminalColumns(out))
 
        contents, err := os.ReadFile(out.Name())
@@ -173,6 +175,7 @@ func TestTerminalStylesOnlyRepliesAndWork(t *testing.T) {
                Event{Kind: "busy"},
                Event{Kind: "idle"},
        )
                Event{Kind: "busy"},
                Event{Kind: "idle"},
        )
+
        require.Equal(t, "[working]\n\x1b[1mfirst next\x1b[0m\n\x1b[1mlast\x1b[0m\n[BlueBird]\n\x1b[1mReport\x1b[0m\n"+
                "[tool] **raw tool**\n[result] **raw result**\n[error] **raw error**\n**raw notice**\n[telegram] **raw input**\n"+
                "[connected to **raw agent**]\n[BlueBird] started\n[working]\n[done]\n> ", out.String())
        require.Equal(t, "[working]\n\x1b[1mfirst next\x1b[0m\n\x1b[1mlast\x1b[0m\n[BlueBird]\n\x1b[1mReport\x1b[0m\n"+
                "[tool] **raw tool**\n[result] **raw result**\n[error] **raw error**\n**raw notice**\n[telegram] **raw input**\n"+
                "[connected to **raw agent**]\n[BlueBird] started\n[working]\n[done]\n> ", out.String())
@@ -193,6 +196,7 @@ func TestTerminalZeroWidthUsesEightyAndReadsChanges(t *testing.T) {
                Kind: "reply",
                Text: strings.Repeat("a", 81),
        })
                Kind: "reply",
                Text: strings.Repeat("a", 81),
        })
+
        require.Equal(t, strings.Repeat("a", 80)+"\na\n> ", out.String())
        require.NoError(t, unix.IoctlSetWinsize(fd, unix.TIOCSWINSZ, &unix.Winsize{Col: 40}))
        require.Equal(t, 40, terminalColumns(&out))
        require.Equal(t, strings.Repeat("a", 80)+"\na\n> ", out.String())
        require.NoError(t, unix.IoctlSetWinsize(fd, unix.TIOCSWINSZ, &unix.Winsize{Col: 40}))
        require.Equal(t, 40, terminalColumns(&out))
@@ -241,6 +245,7 @@ func TestTerminalPromptsAfterIdleEventsAndClearsBeforeShownEvents(t *testing.T)
                        Text: "Idle error",
                },
        )
                        Text: "Idle error",
                },
        )
+
        require.Equal(t, "[connected to Example]\n> "+
                "\r\x1b[2KIdle notice\n> "+
                "\r\x1b[2K\x1b[1mIdle report\x1b[0m\n> "+
        require.Equal(t, "[connected to Example]\n> "+
                "\r\x1b[2KIdle notice\n> "+
                "\r\x1b[2K\x1b[1mIdle report\x1b[0m\n> "+
@@ -275,6 +280,7 @@ func TestTerminalNonterminalEventsHaveNoPromptOrClearCommands(t *testing.T) {
                        Text: "Idle again",
                },
        )
                        Text: "Idle again",
                },
        )
+
        require.Equal(t, "[connected to Example]\nIdle notice\n[working]\nBusy notice\n**Answer**\n[done]\nIdle again\n", out.String())
 }
 
        require.Equal(t, "[connected to Example]\nIdle notice\n[working]\nBusy notice\n**Answer**\n[done]\nIdle again\n", out.String())
 }
 
index 11e208d49e82dd4b8c51b128d3558eba9e7c48af..94885ff43c01d45538518a70c3fe3544cb211f69 100644 (file)
--- a/tools.go
+++ b/tools.go
@@ -494,9 +494,11 @@ func (b *Toolbox) shell(ctx context.Context, command string) string {
        cmd.SysProcAttr = &syscall.SysProcAttr{
                Setpgid: true,
        }
        cmd.SysProcAttr = &syscall.SysProcAttr{
                Setpgid: true,
        }
+
        cmd.Cancel = func() error {
                return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
        }
        cmd.Cancel = func() error {
                return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
        }
+
        cmd.WaitDelay = toolShellOutputTimeout
 
        output := &ToolHeadTail{}
        cmd.WaitDelay = toolShellOutputTimeout
 
        output := &ToolHeadTail{}
index e1b19bbb05a125026654985d4240aabf37aac52b..23507f61785bb3e962529169274d9670999cf4aa 100644 (file)
@@ -29,6 +29,7 @@ func TestToolRunsAfterItsCallIsDurable(t *testing.T) {
                "flush, marker absent",
                "echo: done\n",
        }, log.Events())
                "flush, marker absent",
                "echo: done\n",
        }, log.Events())
+
        require.FileExists(t, filepath.Join(dir, "ran"))
 }
 
        require.FileExists(t, filepath.Join(dir, "ran"))
 }
 
index d927a9eda6bbafec3fe257504af28cecfe1aafd2..2f23d4d22d3eebcd182fb20a06a10da895dff9fc 100644 (file)
@@ -72,6 +72,7 @@ func TestTreeReopenLoadsTheSameNodes(t *testing.T) {
                treeTestNode(0, 0, "user: a"),
                treeTestNode(1, 0, "user: a\nreply: b"),
        }
                treeTestNode(0, 0, "user: a"),
                treeTestNode(1, 0, "user: a\nreply: b"),
        }
+
        for _, node := range nodes {
                require.NoError(t, tree.Add(node))
        }
        for _, node := range nodes {
                require.NoError(t, tree.Add(node))
        }
@@ -107,6 +108,7 @@ func TestTreeBuildsAMissingIndexAgain(t *testing.T) {
                treeTestNode(0, 0, "user: a"),
                treeTestNode(1, 0, "user: a\nreply: b"),
        }
                treeTestNode(0, 0, "user: a"),
                treeTestNode(1, 0, "user: a\nreply: b"),
        }
+
        for _, node := range nodes {
                require.NoError(t, tree.Add(node))
        }
        for _, node := range nodes {
                require.NoError(t, tree.Add(node))
        }
index 733c9a935d61c7f8ae9a7caca1012e6995ffd5a6..5341de5ba3b261d036af66933146e76278d36286 100644 (file)
@@ -57,6 +57,7 @@ func TestTurnRunsToolsUntilTheFinalAnswer(t *testing.T) {
                "echo: hi\n",
                "reply: It said hi.",
        }, test.Log(t))
                "echo: hi\n",
                "reply: It said hi.",
        }, test.Log(t))
+
        require.Equal(t, []string{"user", "busy", "tool", "echo", "reply", "idle"}, test.events.Kinds())
 
        var turns []string
        require.Equal(t, []string{"user", "busy", "tool", "echo", "reply", "idle"}, test.events.Kinds())
 
        var turns []string
index 976d66b6f282216f53489f4fdce8f142d27a1aae..732b50aabda01e51bfcadb324e347869747c16a5 100644 (file)
@@ -80,6 +80,7 @@ func (v *VoiceTranscriber) transcribe(ctx context.Context, path string) (text st
        output := filepath.Join(dir, "text")
        command := exec.CommandContext(ctx, program, "--model", model, "--file", path,
                "--language", "auto", "--max-context", "0", "--output-txt", "--output-file", output)
        output := filepath.Join(dir, "text")
        command := exec.CommandContext(ctx, program, "--model", model, "--file", path,
                "--language", "auto", "--max-context", "0", "--output-txt", "--output-file", output)
+
        command.Stdout = io.Discard
        command.Stderr = io.Discard
        command.WaitDelay = voiceProcessExitTimeout
        command.Stdout = io.Discard
        command.Stderr = io.Discard
        command.WaitDelay = voiceProcessExitTimeout
index c9e24bc5903c51fdd12fbcb670917952da010fe8..c04d091aab39ea70239dc2f13321d3304e22027e 100644 (file)
@@ -29,6 +29,7 @@ printf '  A made-up voice transcript.\n' > "$output.txt"
 printf 'private speech in process output'
 printf 'private speech in process errors' >&2
 `)
 printf 'private speech in process output'
 printf 'private speech in process errors' >&2
 `)
+
        data := []byte("made-up audio")
        text := transcriber.Text(t.Context(), store, data)
        require.Contains(t, text, "[voice ")
        data := []byte("made-up audio")
        text := transcriber.Text(t.Context(), store, data)
        require.Contains(t, text, "[voice ")
@@ -70,6 +71,7 @@ func TestVoiceFailuresKeepStoredAudioAndSafeMarkers(t *testing.T) {
                        script: "while [ \"$1\" != --output-file ]; do shift; done; shift; touch \"$1.txt\"",
                },
        }
                        script: "while [ \"$1\" != --output-file ]; do shift; done; shift; touch \"$1.txt\"",
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        store, transcriber := voiceTestInput(t, tc.script)
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        store, transcriber := voiceTestInput(t, tc.script)
@@ -208,6 +210,7 @@ func TestVoiceLookupHonorsExplicitThenNixThenPath(t *testing.T) {
                        program: valid.Transcriber,
                },
        }
                        program: valid.Transcriber,
                },
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        program, model, err := voiceLookup(tc.config, tc.program, tc.model)
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        program, model, err := voiceLookup(tc.config, tc.program, tc.model)
index ee785a36c19501197ebac0accf76100998ee3353..738ef8de1a09845e41422992bb4ccff9f8956f46 100644 (file)
@@ -279,7 +279,8 @@ func (w *WhatsApp) Run(ctx context.Context, l *TurnLoop, notify func(Event)) err
 
        var pairing sync.WaitGroup
 
 
        var pairing sync.WaitGroup
 
-       cancel := func() {}
+       cancel := func() {
+       }
        defer func() {
                cancel()
                pairing.Wait()
        defer func() {
                cancel()
                pairing.Wait()
@@ -316,7 +317,8 @@ func (w *WhatsApp) Run(ctx context.Context, l *TurnLoop, notify func(Event)) err
                        cancel()
                        pairing.Wait()
 
                        cancel()
                        pairing.Wait()
 
-                       cancel = func() {}
+                       cancel = func() {
+                       }
 
                        switch {
                        case request.unlink:
 
                        switch {
                        case request.unlink:
index 4bd0a1f3354db5b41b2bab288f9af598cd4343bd..553a2cbc288cc365a99a98a72fbb0149fd91b962 100644 (file)
@@ -486,6 +486,7 @@ func TestWhatsAppImportChildPlanReadsRootCutoffButNotRootImportedDays(t *testing
                        Date: "2025-01-03T10:20:00Z",
                },
        })
                        Date: "2025-01-03T10:20:00Z",
                },
        })
+
        rebuildTestLog(t, childDir, []DatedMessage{
                {
                        Kind: "work",
        rebuildTestLog(t, childDir, []DatedMessage{
                {
                        Kind: "work",
@@ -529,6 +530,7 @@ func TestWhatsAppImportChildRebuildUsesOwnPromptAndCache(t *testing.T) {
                Text: "Keep the root history.",
                Date: "2025-02-01T00:00:00Z",
        }})
                Text: "Keep the root history.",
                Date: "2025-02-01T00:00:00Z",
        }})
+
        rebuildTestLog(t, childDir, []DatedMessage{{
                Kind: "user",
                Text: "Keep the child history.",
        rebuildTestLog(t, childDir, []DatedMessage{{
                Kind: "user",
                Text: "Keep the child history.",
index 85b7d15b6f82535f2909c950bb1b9f536848c272..a2e9a926a5c40044b266902f0e1471828715cee1 100644 (file)
@@ -335,6 +335,7 @@ func TestWhatsAppImageFailureKeepsNoSecretsAndDoesNotRetry(t *testing.T) {
                },
                {name: "send failure stores a safe cause"},
        }
                },
                {name: "send failure stores a safe cause"},
        }
+
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        whatsapp, fake := whatsappStartTestLinked(t)
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
                        whatsapp, fake := whatsappStartTestLinked(t)
@@ -432,6 +433,7 @@ func TestTelegramReceivedPhotoCanBeShownAndSentToWhatsApp(t *testing.T) {
                        bot:   bot,
                },
        }
                        bot:   bot,
                },
        }
+
        require.NotContains(t, box.run(t.Context(), "image_show", `{"image":"`+names[0]+`","caption":"The plan."}`), "error:")
        require.Contains(t, box.run(t.Context(), "whatsapp_send", `{"id":"forward","chat":"15550002222@s.whatsapp.net","text":"The plan.","image":"`+names[0]+`"}`), "Sent to")
        require.Equal(t, [][]byte{picture}, fake.Uploads())
        require.NotContains(t, box.run(t.Context(), "image_show", `{"image":"`+names[0]+`","caption":"The plan."}`), "error:")
        require.Contains(t, box.run(t.Context(), "whatsapp_send", `{"id":"forward","chat":"15550002222@s.whatsapp.net","text":"The plan.","image":"`+names[0]+`"}`), "Sent to")
        require.Equal(t, [][]byte{picture}, fake.Uploads())
index 825fa189e307aafe2dafc6733cdc56e7eb34dbea..269f46d1b8efaf9effbaaf8ba1c19f728454a4a7 100644 (file)
@@ -139,6 +139,7 @@ func (s *WhatsAppState) Add(chat WhatsAppChat, message WhatsAppMessage, arrival
                        Chat:   chat,
                        First:  arrival,
                })
                        Chat:   chat,
                        First:  arrival,
                })
+
                index = len(s.file.Batches) - 1
        }
 
                index = len(s.file.Batches) - 1
        }
 
index 9ed4105130920aca9b8cd402a029cde811a12a3c..05ea123dea3c577b9d6c7575cd1c1bbad6d64b61 100644 (file)
@@ -124,6 +124,7 @@ func TestWhatsAppLinkedPairRequestKeepsFirstConnectionRetries(t *testing.T) {
                test.whatsapp.Command("pair 15550001111", func(text string) {
                        answers <- text
                })
                test.whatsapp.Command("pair 15550001111", func(text string) {
                        answers <- text
                })
+
                require.Equal(t, "WhatsApp is linked already. Run /whatsapp unlink first.", <-answers)
                synctest.Wait()
                test.fake.mutex.Lock()
                require.Equal(t, "WhatsApp is linked already. Run /whatsapp unlink first.", <-answers)
                synctest.Wait()
                test.fake.mutex.Lock()
@@ -194,6 +195,7 @@ func TestWhatsAppPairsAfterFirstConnectionRetriesStop(t *testing.T) {
                synctest.Wait()
                test.whatsapp.Command("unlink", func(_ string) {
                })
                synctest.Wait()
                test.whatsapp.Command("unlink", func(_ string) {
                })
+
                synctest.Wait()
                require.False(t, test.whatsapp.isLinked())
 
                synctest.Wait()
                require.False(t, test.whatsapp.isLinked())
 
@@ -316,6 +318,7 @@ func TestWhatsAppLogsEachChangeOfTheConnectionOnce(t *testing.T) {
                "<4>disconnected, retrying",
                "<6>connected again after=1m25s",
        }, strings.Split(strings.TrimSuffix(logs.String(), "\n"), "\n"))
                "<4>disconnected, retrying",
                "<6>connected again after=1m25s",
        }, strings.Split(strings.TrimSuffix(logs.String(), "\n"), "\n"))
+
        require.NotContains(t, logs.String(), whatsappTestNumber)
        require.NotContains(t, logs.String(), whatsappTestLID)
 }
        require.NotContains(t, logs.String(), whatsappTestNumber)
        require.NotContains(t, logs.String(), whatsappTestLID)
 }
@@ -663,6 +666,7 @@ func TestWhatsAppLogsNoOwnNumber(t *testing.T) {
        require.Eventually(t, func() bool {
                return logs.String() != ""
        }, 10*time.Second, time.Millisecond)
        require.Eventually(t, func() bool {
                return logs.String() != ""
        }, 10*time.Second, time.Millisecond)
+
        require.Equal(t, "<4>first connect failed, retrying: no route for OWN@s.whatsapp.net and OWN@lid\n", logs.String())
 }
 
        require.Equal(t, "<4>first connect failed, retrying: no route for OWN@s.whatsapp.net and OWN@lid\n", logs.String())
 }
 
index 305afdf01f628eb9b2de5884a268ba5e2c0e24a5..5210823e4f5fbe702334008b395c441035ab0bd7 100644 (file)
@@ -50,6 +50,7 @@ func TestWhatsAppVoiceRoutingSurvivesRestartWithoutDuplicateEntries(t *testing.T
        server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
        }
        server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
                modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
        }
+
        first := newDaemonMessageTest(t, dir, server)
        whatsappTestSession(t, dir, true)
 
        first := newDaemonMessageTest(t, dir, server)
        whatsappTestSession(t, dir, true)