Add ephemeral channel join summary feature - #879
Conversation
When enabled, joining a public or private channel shows the joining user a private, ephemeral LLM-generated summary of the channel's recent activity. Only the joining user sees it and it is not persisted. DMs, group messages, and archived channels are skipped, as are bot users and channels with too little recent activity. The new joinsummary service is wired to the UserHasJoinedChannel hook. Cheap gating runs synchronously; the LLM completion runs in a goroutine so joins are never blocked. Configurable via System Console (enable, summary agent, lookback days, minimum messages) behind a default-off toggle.
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
📝 WalkthroughWalkthroughThis PR adds a channel join catch-up summary feature: a new config schema, a ChangesChannel Join Summary
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Mattermost
participant Plugin
participant Service as joinsummary.Service
participant LLM
Mattermost->>Plugin: UserHasJoinedChannel(channelMember)
Plugin->>Service: HandleUserJoinedChannel(ctx, channelMember)
Service->>Service: shouldSummarizeChannel(channel)
alt eligible
Service->>Service: spawn goroutine generateAndSend
Service->>Mattermost: fetch recent posts since lookback
Service->>Service: filterSummarizablePosts(posts)
alt enough posts
Service->>LLM: ChatCompletionNoStream(prompt)
LLM-->>Service: summary text
Service->>Mattermost: send ephemeral post to joining user
end
end
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
joinsummary/joinsummary.go (1)
116-178: 🧹 Nitpick | 🔵 TrivialLGTM! The flow from post fetch through LLM completion to ephemeral delivery is well-structured, with appropriate error handling at each step.
One operational note:
GetPostsSincemay return a large number of posts for very active channels within the lookback window. All of them are formatted viaformat.ThreadDataand sent to the LLM in a single completion. Consider adding a configurable maximum post cap (e.g., 200) to bound token cost and prevent context-window overflow, especially since admins can setLookbackDaysto large values.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@joinsummary/joinsummary.go` around lines 116 - 178, `generateAndSend` currently sends every post returned by `GetPostsSince` through `format.ThreadData` into a single LLM request, which can spike token usage and overflow the context window for busy channels. Add a configurable maximum post cap (for example via `config.ChannelJoinSummaryConfig` or a helper used in `generateAndSend`) and trim `threadData.Posts` before formatting and building the `llm.CompletionRequest`, while keeping the existing min-posts and error-handling flow intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@server/main.go`:
- Around line 616-621: `UserHasJoinedChannel` is starting the join-summary LLM
path with `context.Background()`, which prevents cancellation during plugin
shutdown. Add a plugin-level cancellable `context.Context` on `Plugin`
(initialized in `OnActivate` and canceled in `OnDeactivate`) and pass that
context into `joinSummaryService.HandleUserJoinedChannel` instead of creating a
background context. Keep the context threaded through the existing
`HandleUserJoinedChannel` → `generateAndSend` → `ChatCompletionNoStream` path so
the request can stop cleanly when the plugin deactivates.
---
Nitpick comments:
In `@joinsummary/joinsummary.go`:
- Around line 116-178: `generateAndSend` currently sends every post returned by
`GetPostsSince` through `format.ThreadData` into a single LLM request, which can
spike token usage and overflow the context window for busy channels. Add a
configurable maximum post cap (for example via `config.ChannelJoinSummaryConfig`
or a helper used in `generateAndSend`) and trim `threadData.Posts` before
formatting and building the `llm.CompletionRequest`, while keeping the existing
min-posts and error-handling flow intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: 0687480d-34fd-446a-8083-05f3b9a48ddb
📒 Files selected for processing (9)
config/config.godocs/admin_guide.mdjoinsummary/joinsummary.gojoinsummary/joinsummary_test.goserver/main.gowebapp/src/components/system_console/channel_join_summary/channel_join_summary_panel.tsxwebapp/src/components/system_console/config.tsxwebapp/src/components/system_console/plugin_config_types.tsxwebapp/src/i18n/en.json
| func (p *Plugin) UserHasJoinedChannel(c *plugin.Context, channelMember *model.ChannelMember, actor *model.User) { | ||
| if p.joinSummaryService == nil { | ||
| return | ||
| } | ||
| p.joinSummaryService.HandleUserJoinedChannel(context.Background(), channelMember, actor) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Replace context.Background() with a plugin-level cancellable context.
The UserHasJoinedChannel hook is an entry point to an LLM call path (HandleUserJoinedChannel → generateAndSend → ChatCompletionNoStream). The coding guidelines state: "Thread ctx context.Context as the first parameter through every entry point to LLM call code paths; never introduce context.Background() shortcuts in production code." Using context.Background() means the async goroutine cannot be cancelled during plugin deactivation—it will run until the 2-minute summaryTimeout expires.
Consider creating a plugin-level context.Context in OnActivate (cancelled in OnDeactivate) and passing it here instead. This enables clean shutdown and satisfies the guideline.
As per coding guidelines: "Thread ctx context.Context as the first parameter through every entry point to LLM call code paths; never introduce context.Background() shortcuts in production code."
🔧 Suggested fix: plugin-level cancellable context
type Plugin struct {
plugin.MattermostPlugin
configuration config.Container
+ pluginCtx context.Context
+ pluginCancel context.CancelFunc
// ... existing fields
}
func (p *Plugin) OnActivate() error {
+ p.pluginCtx, p.pluginCancel = context.WithCancel(context.Background())
// ... existing activation code
return nil
}
+func (p *Plugin) OnDeactivate() error {
+ if p.pluginCancel != nil {
+ p.pluginCancel()
+ }
+ return nil
+}
func (p *Plugin) UserHasJoinedChannel(c *plugin.Context, channelMember *model.ChannelMember, actor *model.User) {
if p.joinSummaryService == nil {
return
}
- p.joinSummaryService.HandleUserJoinedChannel(context.Background(), channelMember, actor)
+ p.joinSummaryService.HandleUserJoinedChannel(p.pluginCtx, channelMember, actor)
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/main.go` around lines 616 - 621, `UserHasJoinedChannel` is starting
the join-summary LLM path with `context.Background()`, which prevents
cancellation during plugin shutdown. Add a plugin-level cancellable
`context.Context` on `Plugin` (initialized in `OnActivate` and canceled in
`OnDeactivate`) and pass that context into
`joinSummaryService.HandleUserJoinedChannel` instead of creating a background
context. Keep the context threaded through the existing
`HandleUserJoinedChannel` → `generateAndSend` → `ChatCompletionNoStream` path so
the request can stop cleanly when the plugin deactivates.
Source: Coding guidelines
|
This PR has been automatically labelled "stale" because it hasn't had recent activity. |
This pull request introduces a new feature for generating and delivering an ephemeral "catch-up" summary to users when they join a channel. The summary is generated by an LLM agent based on recent channel activity and is visible only to the joining user. The implementation includes backend logic, configuration options, API integration, a web-based admin panel, and documentation updates.
The most important changes are:
Channel Join Summary Feature Implementation
joinsummarypackage, which handles generation and delivery of ephemeral channel summaries when a user joins a channel. It fetches recent messages, filters out system/deleted posts, invokes the LLM agent, and sends the summary as an ephemeral post. Includes tests for core logic. (joinsummary/joinsummary.go,joinsummary/joinsummary_test.go) [1] [2]Configuration and Integration
config.Config) with a newChannelJoinSummaryConfigstruct, exposing options for enabling the feature, selecting the agent bot, lookback window, and minimum post count. Provided a getter for the new config. (config/config.go) [1] [2] [3]UserHasJoinedChannelevent, ensuring asynchronous, best-effort operation. (server/main.go) [1] [2] [3] [4] [5]Admin UI and Documentation
webapp/src/components/system_console/channel_join_summary/channel_join_summary_panel.tsx)docs/admin_guide.md)Summary by CodeRabbit
New Features
Bug Fixes