From ea574723dff01db12a18983c9869ec3f9763ffbe Mon Sep 17 00:00:00 2001 From: rabbitblood Date: Fri, 17 Apr 2026 14:55:53 -0700 Subject: [PATCH] =?UTF-8?q?fix(slack):=20restore=20FetchChannelHistory=20?= =?UTF-8?q?=E2=80=94=20was=20lost=20during=20branch=20juggling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function was defined on a feature branch, referenced by manager.go and slack_test.go, but never made it to main after the rebase. This caused go build to fail with 'undefined: FetchChannelHistory', which Docker masked by using a cached binary from the last successful build. That cached binary had neither the mrkdwn blocks nor the Level 3 context injection — explaining why Slack messages showed raw markdown despite the source having the converter. Co-Authored-By: Claude Opus 4.6 (1M context) --- platform/internal/channels/slack.go | 55 +++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/platform/internal/channels/slack.go b/platform/internal/channels/slack.go index 734e8577..b2ef8df4 100644 --- a/platform/internal/channels/slack.go +++ b/platform/internal/channels/slack.go @@ -334,6 +334,61 @@ func (s *SlackAdapter) ParseWebhook(c *gin.Context, _ map[string]interface{}) (* }, nil } +// SlackHistoryMessage represents a single message from conversations.history. +type SlackHistoryMessage struct { + User string `json:"user"` + Username string `json:"username"` + Text string `json:"text"` + Ts string `json:"ts"` + BotID string `json:"bot_id"` +} + +// FetchChannelHistory calls Slack conversations.history and returns the +// last N messages from the channel, filtering out raw bot messages. +func FetchChannelHistory(ctx context.Context, botToken, channelID string, limit int) ([]SlackHistoryMessage, error) { + if botToken == "" || channelID == "" { + return nil, nil + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, + fmt.Sprintf("https://slack.com/api/conversations.history?channel=%s&limit=%d", channelID, limit*2), + nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+botToken) + + resp, err := slackHTTPClient.Do(req) + if err != nil { + return nil, err + } + body, _ := io.ReadAll(io.LimitReader(resp.Body, 65536)) + resp.Body.Close() + + var result struct { + OK bool `json:"ok"` + Messages []SlackHistoryMessage `json:"messages"` + } + if json.Unmarshal(body, &result) != nil || !result.OK { + return nil, fmt.Errorf("slack history API error") + } + + var filtered []SlackHistoryMessage + for _, m := range result.Messages { + if m.BotID != "" && m.Username == "" { + continue + } + filtered = append(filtered, m) + if len(filtered) >= limit { + break + } + } + // Reverse: oldest first + for i, j := 0, len(filtered)-1; i < j; i, j = i+1, j-1 { + filtered[i], filtered[j] = filtered[j], filtered[i] + } + return filtered, nil +} + // StartPolling returns nil immediately. Slack does not support long-polling // for Incoming Webhooks — use the Slack Events API + webhook route instead. func (s *SlackAdapter) StartPolling(_ context.Context, _ map[string]interface{}, _ MessageHandler) error {