From ee47513b4d37d2e48296e30a2a5c9dc0607e0eaf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:05:50 +0000 Subject: [PATCH] fix: slash command responses no longer overwrite the ongoing task status bubble Co-authored-by: dj-oyu <68707227+dj-oyu@users.noreply.github.com> --- pkg/agent/loop.go | 7 +++--- pkg/agent/loop_test.go | 51 ++++++++++++++++++++++++++++++++++++++++ pkg/bus/types.go | 9 +++---- pkg/channels/telegram.go | 16 +++++++------ 4 files changed, 69 insertions(+), 14 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 87d72b116..879db8d45 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -186,9 +186,10 @@ func (al *AgentLoop) Run(ctx context.Context) error { if response, handled := al.handleCommand(ctx, msg); handled { if response != "" { al.bus.PublishOutbound(bus.OutboundMessage{ - Channel: msg.Channel, - ChatID: msg.ChatID, - Content: response, + Channel: msg.Channel, + ChatID: msg.ChatID, + Content: response, + SkipPlaceholder: true, }) } continue diff --git a/pkg/agent/loop_test.go b/pkg/agent/loop_test.go index 8abc2f142..fb8de7a15 100644 --- a/pkg/agent/loop_test.go +++ b/pkg/agent/loop_test.go @@ -798,6 +798,57 @@ func TestResolveProvider_EmptyNameReturnsFallback(t *testing.T) { } } +// TestSlashCommandResponseSkipsPlaceholder verifies that slash command responses +// are published with SkipPlaceholder=true so they don't overwrite the ongoing task +// status bubble. +func TestSlashCommandResponseSkipsPlaceholder(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "agent-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + cfg := &config.Config{ + Agents: config.AgentsConfig{ + Defaults: config.AgentDefaults{ + Workspace: tmpDir, + Model: "test-model", + MaxTokens: 4096, + MaxToolIterations: 10, + }, + }, + } + + msgBus := bus.NewMessageBus() + provider := &mockProvider{} + al := NewAgentLoop(cfg, msgBus, provider) + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + go func() { + _ = al.Run(ctx) + }() + + // Send a slash command + msgBus.PublishInbound(bus.InboundMessage{ + Channel: "telegram", + SenderID: "user1", + ChatID: "chat1", + Content: "/todo", + }) + + // Read the outbound message + outMsg, ok := msgBus.SubscribeOutbound(ctx) + if !ok { + t.Fatal("expected outbound message from slash command") + } + + if !outMsg.SkipPlaceholder { + t.Errorf("expected SkipPlaceholder=true for slash command response, got false") + } +} + func TestBuildTaskReminder_Truncation(t *testing.T) { // Build a long message (1000 runes) longMsg := strings.Repeat("あ", 1000) diff --git a/pkg/bus/types.go b/pkg/bus/types.go index 84f9458ed..debe1fc57 100644 --- a/pkg/bus/types.go +++ b/pkg/bus/types.go @@ -11,10 +11,11 @@ type InboundMessage struct { } type OutboundMessage struct { - Channel string `json:"channel"` - ChatID string `json:"chat_id"` - Content string `json:"content"` - IsStatus bool `json:"is_status,omitempty"` + Channel string `json:"channel"` + ChatID string `json:"chat_id"` + Content string `json:"content"` + IsStatus bool `json:"is_status,omitempty"` + SkipPlaceholder bool `json:"skip_placeholder,omitempty"` } type MessageHandler func(InboundMessage) error diff --git a/pkg/channels/telegram.go b/pkg/channels/telegram.go index 130e1812c..abcc941ba 100644 --- a/pkg/channels/telegram.go +++ b/pkg/channels/telegram.go @@ -201,15 +201,17 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err // Try to edit placeholder firstChunkSent := false - if pID, ok := c.placeholders.Load(msg.ChatID); ok { - c.placeholders.Delete(msg.ChatID) - editMsg := tu.EditMessageText(tu.ID(chatID), pID.(int), markdownToTelegramHTML(chunks[0])) - editMsg.ParseMode = telego.ModeHTML + if !msg.SkipPlaceholder { + if pID, ok := c.placeholders.Load(msg.ChatID); ok { + c.placeholders.Delete(msg.ChatID) + editMsg := tu.EditMessageText(tu.ID(chatID), pID.(int), markdownToTelegramHTML(chunks[0])) + editMsg.ParseMode = telego.ModeHTML - if _, err = c.bot.EditMessageText(ctx, editMsg); err == nil { - firstChunkSent = true + if _, err = c.bot.EditMessageText(ctx, editMsg); err == nil { + firstChunkSent = true + } + // Fallback to new message if edit fails } - // Fallback to new message if edit fails } sendChunk := func(text string) error {