diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 4c3313d18..e5a8566f4 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -740,7 +740,7 @@ func (al *AgentLoop) maybeSummarize(sessionKey, channel, chatID string) { al.bus.PublishOutbound(bus.OutboundMessage{ Channel: channel, ChatID: chatID, - Content: "⚠️ Memory threshold reached. Optimizing conversation history...", + Content: "🔄 Context optimized. (Memory threshold reached)", }) } al.summarizeSession(sessionKey) diff --git a/pkg/channels/discord.go b/pkg/channels/discord.go index 00aa8ab4d..51b12e8f1 100644 --- a/pkg/channels/discord.go +++ b/pkg/channels/discord.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "os" - "strings" "time" "github.com/bwmarrin/discordgo" @@ -120,97 +119,102 @@ func (c *DiscordChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro // splitMessage splits long messages into chunks, preserving code block integrity // Uses natural boundaries (newlines, spaces) and extends messages slightly to avoid breaking code blocks func splitMessage(content string, limit int) []string { - var messages []string + if limit > 1900 { + limit = 1900 + } + runes := []rune(content) + if len(runes) <= limit { + return []string{content} + } - for len(content) > 0 { - if len(content) <= limit { - messages = append(messages, content) + var chunks []string + for len(runes) > 0 { + if len(runes) <= limit { + chunks = append(chunks, string(runes)) break } - msgEnd := limit + splitAt := limit - // Find natural split point within the limit - msgEnd = findLastNewline(content[:limit], 200) - if msgEnd <= 0 { - msgEnd = findLastSpace(content[:limit], 100) - } - if msgEnd <= 0 { - msgEnd = limit + // Look for natural split points (newlines) in a window + window := 300 + if splitAt < window { + window = splitAt } - // Check if this would end with an incomplete code block - candidate := content[:msgEnd] - unclosedIdx := findLastUnclosedCodeBlock(candidate) + foundNatural := false + for i := splitAt - 1; i >= splitAt-window; i-- { + if runes[i] == '\n' { + splitAt = i + 1 + foundNatural = true + break + } + } - if unclosedIdx >= 0 { - // Message would end with incomplete code block - // Try to extend to include the closing ``` (with some buffer) - extendedLimit := limit + 500 // Allow 500 char buffer for code blocks - if len(content) > extendedLimit { - closingIdx := findNextClosingCodeBlock(content, msgEnd) - if closingIdx > 0 && closingIdx <= extendedLimit { - // Extend to include the closing ``` - msgEnd = closingIdx - } else { - // Can't find closing, split before the code block - msgEnd = findLastNewline(content[:unclosedIdx], 200) - if msgEnd <= 0 { - msgEnd = findLastSpace(content[:unclosedIdx], 100) - } - if msgEnd <= 0 { - msgEnd = unclosedIdx + if !foundNatural { + for i := splitAt - 1; i >= splitAt-window/2; i-- { + if runes[i] == ' ' || runes[i] == '\t' { + splitAt = i + 1 + foundNatural = true + break + } + } + } + + // Check for unclosed code blocks + chunkCandidate := runes[:splitAt] + if isInsideCodeBlock(chunkCandidate) { + // Try to find the closing code block within extended limit + extendedLimit := limit + 300 + if extendedLimit > 2000 { + extendedLimit = 2000 + } + + foundClosing := false + for i := splitAt; i < len(runes)-2 && i < extendedLimit-3; i++ { + if runes[i] == '`' && runes[i+1] == '`' && runes[i+2] == '`' { + splitAt = i + 3 + foundClosing = true + break + } + } + + if !foundClosing { + // Can't find closing within reasonable limit, split before the block + for i := splitAt - 1; i >= 0; i-- { + if i+2 < len(runes) && runes[i] == '`' && runes[i+1] == '`' && runes[i+2] == '`' { + if i > 0 { + splitAt = i + } + break } } - } else { - // Remaining content fits within extended limit - msgEnd = len(content) } } - if msgEnd <= 0 { - msgEnd = limit + if splitAt <= 0 { + splitAt = limit } - messages = append(messages, content[:msgEnd]) - content = strings.TrimSpace(content[msgEnd:]) + chunks = append(chunks, string(runes[:splitAt])) + runes = runes[splitAt:] } - return messages + return chunks } -// findLastUnclosedCodeBlock finds the last opening ``` that doesn't have a closing ``` -// Returns the position of the opening ``` or -1 if all code blocks are complete -func findLastUnclosedCodeBlock(text string) int { +func isInsideCodeBlock(runes []rune) bool { count := 0 - lastOpenIdx := -1 - - for i := 0; i < len(text); i++ { - if i+2 < len(text) && text[i] == '`' && text[i+1] == '`' && text[i+2] == '`' { - if count == 0 { - lastOpenIdx = i - } + for i := 0; i < len(runes)-2; i++ { + if runes[i] == '`' && runes[i+1] == '`' && runes[i+2] == '`' { count++ i += 2 } } - - // If odd number of ``` markers, last one is unclosed - if count%2 == 1 { - return lastOpenIdx - } - return -1 + return count%2 != 0 } -// findNextClosingCodeBlock finds the next closing ``` starting from a position -// Returns the position after the closing ``` or -1 if not found -func findNextClosingCodeBlock(text string, startIdx int) int { - for i := startIdx; i < len(text); i++ { - if i+2 < len(text) && text[i] == '`' && text[i+1] == '`' && text[i+2] == '`' { - return i + 3 - } - } - return -1 +func _unused_marker_() { } // findLastNewline finds the last newline character within the last N characters