From 6bfbfe06c1e6c04778a317591e9671219f28fb84 Mon Sep 17 00:00:00 2001 From: Viet Tran Date: Tue, 24 Mar 2026 21:27:41 +0700 Subject: [PATCH] fix(slack): enable thread-based session isolation for DMs (AI Panel support) (#449) Remove the !isDM restriction in the Slack handler, allowing thread_ts to be propagated for DMs. Add BuildScopedThreadSessionKey for string-based thread IDs (Slack timestamps) and update the message consumer to detect thread-scoped local_key values and override the session key. This enables Slack's AI Panel "New Chat" to correctly start fresh GoClaw sessions with clean history. Closes #435 Co-authored-by: Erudition --- cmd/gateway_consumer_normal.go | 8 ++++++++ internal/channels/slack/handlers.go | 2 +- internal/sessions/key.go | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/cmd/gateway_consumer_normal.go b/cmd/gateway_consumer_normal.go index ea95ae05..4022f59f 100644 --- a/cmd/gateway_consumer_normal.go +++ b/cmd/gateway_consumer_normal.go @@ -65,6 +65,14 @@ func processNormalMessage( } sessionKey := sessions.BuildScopedSessionKey(agentID, msg.Channel, sessions.PeerKind(peerKind), msg.ChatID) + // Thread-based isolation override (e.g. Slack DM threads, AI Panel) + if lk := msg.Metadata["local_key"]; lk != "" && strings.Contains(lk, ":thread:") { + parts := strings.SplitN(lk, ":thread:", 2) + if len(parts) == 2 { + sessionKey = sessions.BuildScopedThreadSessionKey(agentID, msg.Channel, sessions.PeerKind(peerKind), msg.ChatID, parts[1]) + } + } + // Forum topic: override session key to isolate per-topic history. // TS ref: buildTelegramGroupPeerId() in src/telegram/bot/helpers.ts if msg.Metadata["is_forum"] == "true" && peerKind == string(sessions.PeerGroup) { diff --git a/internal/channels/slack/handlers.go b/internal/channels/slack/handlers.go index fa165abb..2953c458 100644 --- a/internal/channels/slack/handlers.go +++ b/internal/channels/slack/handlers.go @@ -166,7 +166,7 @@ func (c *Channel) handleMessage(ev *slackevents.MessageEvent) { // Determine local_key and thread context localKey := channelID - if !isDM && threadTS != "" { + if threadTS != "" { localKey = fmt.Sprintf("%s:thread:%s", channelID, threadTS) } diff --git a/internal/sessions/key.go b/internal/sessions/key.go index 3c5d3b1c..2f07853b 100644 --- a/internal/sessions/key.go +++ b/internal/sessions/key.go @@ -59,6 +59,14 @@ func BuildDMThreadSessionKey(agentID, channel, peerID string, threadID int) stri return fmt.Sprintf("agent:%s:%s:direct:%s:thread:%d", agentID, channel, peerID, threadID) } +// BuildScopedThreadSessionKey builds a session key that includes a thread/topic ID. +// Supports string-based thread IDs (e.g. Slack timestamps). +// +// agent:{agentId}:{channel}:{kind}:{chatID}:thread:{threadID} +func BuildScopedThreadSessionKey(agentID, channel string, kind PeerKind, chatID, threadID string) string { + return fmt.Sprintf("agent:%s:%s:%s:%s:thread:%s", agentID, channel, kind, chatID, threadID) +} + // BuildSubagentSessionKey builds the session key for a subagent. // // agent:{agentId}:subagent:{label}