diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 28e549ce0..3533d547f 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -55,6 +55,7 @@ type processOptions struct { SessionKey string // Session identifier for history/context Channel string // Target channel for tool execution ChatID string // Target chat ID for tool execution + ChatName string // Optional human-readable chat name for prompt context UserMessage string // User message content (may include prefix) Media []string // media:// refs from inbound message DefaultResponse string // Response when LLM returns empty @@ -67,6 +68,7 @@ const ( defaultResponse = "I've completed processing but have no response to give. Increase `max_tool_iterations` in config.json." sessionKeyAgentPrefix = "agent:" metadataKeyAccountID = "account_id" + metadataKeyChatName = "chat_name" metadataKeyGuildID = "guild_id" metadataKeyTeamID = "team_id" metadataKeyParentPeerKind = "parent_peer_kind" @@ -619,6 +621,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) SessionKey: sessionKey, Channel: msg.Channel, ChatID: msg.ChatID, + ChatName: inboundMetadata(msg, metadataKeyChatName), UserMessage: msg.Content, Media: msg.Media, DefaultResponse: defaultResponse, @@ -761,7 +764,7 @@ func (al *AgentLoop) runAgentLoop( opts.UserMessage, opts.Media, opts.Channel, - opts.ChatID, + displayChatID(opts.ChatID, opts.ChatName), ) // Resolve media:// refs to base64 data URLs (streaming) @@ -1818,6 +1821,13 @@ func inboundMetadata(msg bus.InboundMessage, key string) string { return msg.Metadata[key] } +func displayChatID(chatID, chatName string) string { + if chatID == "" || chatName == "" { + return chatID + } + return fmt.Sprintf("%s (%s)", chatID, chatName) +} + // extractParentPeer extracts the parent peer (reply-to) from inbound message metadata. func extractParentPeer(msg bus.InboundMessage) *routing.RoutePeer { parentKind := inboundMetadata(msg, metadataKeyParentPeerKind) diff --git a/pkg/agent/loop_test.go b/pkg/agent/loop_test.go index cab82e176..abde81fe3 100644 --- a/pkg/agent/loop_test.go +++ b/pkg/agent/loop_test.go @@ -889,6 +889,16 @@ func TestTargetReasoningChannelID_AllChannels(t *testing.T) { } } +func TestDisplayChatID_AppendsChatName(t *testing.T) { + if got := displayChatID("123456789012345678", "#general"); got != "123456789012345678 (#general)" { + t.Fatalf("displayChatID() = %q, want %q", got, "123456789012345678 (#general)") + } + + if got := displayChatID("123456789012345678", ""); got != "123456789012345678" { + t.Fatalf("displayChatID() without chatName = %q, want %q", got, "123456789012345678") + } +} + func TestHandleReasoning(t *testing.T) { newLoop := func(t *testing.T) (*AgentLoop, *bus.MessageBus) { t.Helper() diff --git a/pkg/channels/discord/discord.go b/pkg/channels/discord/discord.go index fbfcad151..f9c3e5b55 100644 --- a/pkg/channels/discord/discord.go +++ b/pkg/channels/discord/discord.go @@ -449,12 +449,31 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag "display_name": sender.DisplayName, "guild_id": m.GuildID, "channel_id": m.ChannelID, + "chat_name": resolveDiscordChannelName(c.session, m.ChannelID), "is_dm": fmt.Sprintf("%t", m.GuildID == ""), } c.HandleMessage(c.ctx, peer, m.ID, senderID, m.ChannelID, content, mediaPaths, metadata, sender) } +func resolveDiscordChannelName(session *discordgo.Session, channelID string) string { + if session == nil || channelID == "" { + return "" + } + + if session.State != nil { + if channel, err := session.State.Channel(channelID); err == nil && channel != nil && channel.Name != "" { + return "#" + channel.Name + } + } + + channel, err := session.Channel(channelID) + if err != nil || channel == nil || channel.Name == "" { + return "" + } + return "#" + channel.Name +} + // startTyping starts a continuous typing indicator loop for the given chatID. // It stops any existing typing loop for that chatID before starting a new one. func (c *DiscordChannel) startTyping(chatID string) { diff --git a/pkg/channels/discord/discord_resolve_test.go b/pkg/channels/discord/discord_resolve_test.go index 4bc65cc18..bca65f3ca 100644 --- a/pkg/channels/discord/discord_resolve_test.go +++ b/pkg/channels/discord/discord_resolve_test.go @@ -2,6 +2,8 @@ package discord import ( "testing" + + "github.com/bwmarrin/discordgo" ) func TestChannelRefRegex(t *testing.T) { @@ -96,3 +98,24 @@ func TestMsgLinkRegex_MultipleMatches(t *testing.T) { t.Errorf("3rd match = %v, want guild=7 chan=8 msg=9", matches[2]) } } + +func TestResolveDiscordChannelName_FromState(t *testing.T) { + session, err := discordgo.New("Bot test-token") + if err != nil { + t.Fatalf("discordgo.New() error = %v", err) + } + + session.StateEnabled = true + session.State = discordgo.NewState() + session.State.GuildAdd(&discordgo.Guild{ID: "guild-1"}) + session.State.ChannelAdd(&discordgo.Channel{ + ID: "123", + Name: "general", + Type: discordgo.ChannelTypeGuildText, + GuildID: "guild-1", + }) + + if got := resolveDiscordChannelName(session, "123"); got != "#general" { + t.Fatalf("resolveDiscordChannelName() = %q, want %q", got, "#general") + } +}