From bf2ed429b28cb0df5bbb7cd29e21dce5fb49b2a0 Mon Sep 17 00:00:00 2001 From: Alix-007 <267018309+Alix-007@users.noreply.github.com> Date: Tue, 17 Mar 2026 21:38:20 +0800 Subject: [PATCH] fix(cron): publish deliver-false agent replies --- pkg/tools/cron.go | 15 ++++++- pkg/tools/cron_test.go | 89 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) diff --git a/pkg/tools/cron.go b/pkg/tools/cron.go index 154ec75f0..737dc26ad 100644 --- a/pkg/tools/cron.go +++ b/pkg/tools/cron.go @@ -374,7 +374,18 @@ func (t *CronTool) ExecuteJob(ctx context.Context, job *cron.CronJob) string { return fmt.Sprintf("Error: %v", err) } - // Response is automatically sent via MessageBus by AgentLoop - _ = response // Will be sent by AgentLoop + // ProcessDirectWithChannel bypasses the normal inbound loop, so publish the + // returned final text here when the agent produced one. + if strings.TrimSpace(response) == "" { + return "ok" + } + + pubCtx, pubCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer pubCancel() + t.msgBus.PublishOutbound(pubCtx, bus.OutboundMessage{ + Channel: channel, + ChatID: chatID, + Content: response, + }) return "ok" } diff --git a/pkg/tools/cron_test.go b/pkg/tools/cron_test.go index 09d29b6fa..551a03cc0 100644 --- a/pkg/tools/cron_test.go +++ b/pkg/tools/cron_test.go @@ -12,6 +12,39 @@ import ( "github.com/sipeed/picoclaw/pkg/cron" ) +type stubJobExecutor struct { + response string + err error + calls []struct { + content string + sessionKey string + channel string + chatID string + } +} + +func (s *stubJobExecutor) ProcessDirectWithChannel( + _ context.Context, + content, + sessionKey, + channel, + chatID string, +) (string, error) { + s.calls = append(s.calls, struct { + content string + sessionKey string + channel string + chatID string + }{ + content: content, + sessionKey: sessionKey, + channel: channel, + chatID: chatID, + }) + + return s.response, s.err +} + func newTestCronToolWithConfig(t *testing.T, cfg *config.Config) *CronTool { t.Helper() storePath := filepath.Join(t.TempDir(), "cron.json") @@ -234,3 +267,59 @@ func TestCronTool_ExecuteJobPublishesErrorWhenExecDisabled(t *testing.T) { t.Fatalf("expected exec disabled message, got: %s", msg.Content) } } + +func TestCronTool_ExecuteJobPublishesAgentReplyWhenDeliverFalse(t *testing.T) { + tool := newTestCronTool(t) + executor := &stubJobExecutor{response: "remember to stretch"} + tool.executor = executor + + job := &cron.CronJob{ID: "job-123"} + job.Payload.Channel = "discord" + job.Payload.To = "chat-42" + job.Payload.Message = "stretch reminder" + + if got := tool.ExecuteJob(context.Background(), job); got != "ok" { + t.Fatalf("ExecuteJob() = %q, want ok", got) + } + if len(executor.calls) != 1 { + t.Fatalf("executor calls = %d, want 1", len(executor.calls)) + } + if executor.calls[0].sessionKey != "cron-job-123" { + t.Fatalf("sessionKey = %q, want %q", executor.calls[0].sessionKey, "cron-job-123") + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + msg, ok := tool.msgBus.SubscribeOutbound(ctx) + if !ok { + t.Fatal("expected outbound message") + } + if msg.Channel != "discord" || msg.ChatID != "chat-42" { + t.Fatalf("message route = %s/%s, want discord/chat-42", msg.Channel, msg.ChatID) + } + if msg.Content != "remember to stretch" { + t.Fatalf("message content = %q, want %q", msg.Content, "remember to stretch") + } +} + +func TestCronTool_ExecuteJobSkipsBlankAgentReplyWhenDeliverFalse(t *testing.T) { + tool := newTestCronTool(t) + tool.executor = &stubJobExecutor{response: " \n\t"} + + job := &cron.CronJob{ID: "job-blank"} + job.Payload.Channel = "discord" + job.Payload.To = "chat-blank" + job.Payload.Message = "stretch reminder" + + if got := tool.ExecuteJob(context.Background(), job); got != "ok" { + t.Fatalf("ExecuteJob() = %q, want ok", got) + } + + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + + if _, ok := tool.msgBus.SubscribeOutbound(ctx); ok { + t.Fatal("expected no outbound message for blank agent reply") + } +}