Phase 1: Fix RHS panel to fetch existing sessions on mount - Add initial API fetch in useAllStatusUpdates() hook - Allow GET /sessions endpoint without shared secret auth - RHS panel now shows sessions after page refresh Phase 2: Floating widget component (registerRootComponent) - New floating_widget.tsx with auto-show/hide behavior - Draggable, collapsible to pulsing dot with session count - Shows last 5 lines of most recent active session - Position persisted to localStorage - CSS styles using Mattermost theme variables Phase 3: Session cleanup and KV optimization - Add LastUpdateMs field to SessionData for staleness tracking - Set LastUpdateMs on session create and update - Add periodic cleanup goroutine (every 5 min) - Stale active sessions (>30 min no update) marked interrupted - Expired non-active sessions (>1 hr) deleted from KV - Add ListAllSessions and keep ListActiveSessions as helper - Add debug logging to daemon file polling Closes #5
105 lines
2.8 KiB
Go
105 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
)
|
|
|
|
// Plugin implements the Mattermost plugin interface.
|
|
type Plugin struct {
|
|
plugin.MattermostPlugin
|
|
|
|
// configurationLock synchronizes access to the configuration.
|
|
configurationLock sync.RWMutex
|
|
|
|
// configuration is the active plugin configuration.
|
|
configuration *Configuration
|
|
|
|
// store wraps KV store operations for session persistence.
|
|
store *Store
|
|
|
|
// botUserID is the plugin's bot user ID (created on activation).
|
|
botUserID string
|
|
|
|
// stopCleanup signals the cleanup goroutine to stop.
|
|
stopCleanup chan struct{}
|
|
}
|
|
|
|
// OnActivate is called when the plugin is activated.
|
|
func (p *Plugin) OnActivate() error {
|
|
p.store = NewStore(p.API)
|
|
|
|
// Ensure plugin bot user exists
|
|
botID, appErr := p.API.EnsureBotUser(&model.Bot{
|
|
Username: "livestatus",
|
|
DisplayName: "Live Status",
|
|
Description: "OpenClaw Live Status plugin bot",
|
|
})
|
|
if appErr != nil {
|
|
p.API.LogWarn("Failed to ensure bot user", "error", appErr.Error())
|
|
} else {
|
|
p.botUserID = botID
|
|
p.API.LogInfo("Plugin bot user ensured", "botUserID", botID)
|
|
}
|
|
|
|
// Start session cleanup goroutine
|
|
p.stopCleanup = make(chan struct{})
|
|
go p.sessionCleanupLoop()
|
|
|
|
p.API.LogInfo("OpenClaw Live Status plugin activated")
|
|
return nil
|
|
}
|
|
|
|
// sessionCleanupLoop runs periodically to clean up stale and expired sessions.
|
|
func (p *Plugin) sessionCleanupLoop() {
|
|
ticker := time.NewTicker(5 * time.Minute)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
staleThreshold := int64(30 * 60 * 1000) // 30 minutes — active sessions with no update
|
|
expireThreshold := int64(60 * 60 * 1000) // 1 hour — completed/interrupted sessions
|
|
cleaned, expired, err := p.store.CleanStaleSessions(staleThreshold, expireThreshold)
|
|
if err != nil {
|
|
p.API.LogWarn("Session cleanup error", "error", err.Error())
|
|
} else if cleaned > 0 || expired > 0 {
|
|
p.API.LogInfo("Session cleanup completed", "stale_marked", cleaned, "expired_deleted", expired)
|
|
}
|
|
case <-p.stopCleanup:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// getBotUserID returns the plugin's bot user ID.
|
|
func (p *Plugin) getBotUserID() string {
|
|
return p.botUserID
|
|
}
|
|
|
|
// OnDeactivate is called when the plugin is deactivated.
|
|
func (p *Plugin) OnDeactivate() error {
|
|
// Stop cleanup goroutine
|
|
if p.stopCleanup != nil {
|
|
close(p.stopCleanup)
|
|
}
|
|
|
|
// Mark all active sessions as interrupted
|
|
sessions, err := p.store.ListAllSessions()
|
|
if err != nil {
|
|
p.API.LogWarn("Failed to list sessions on deactivate", "error", err.Error())
|
|
} else {
|
|
for _, s := range sessions {
|
|
s.Status = "interrupted"
|
|
_ = p.store.SaveSession(s.SessionKey, s)
|
|
p.broadcastUpdate(s.ChannelID, s)
|
|
}
|
|
}
|
|
|
|
p.API.LogInfo("OpenClaw Live Status plugin deactivated")
|
|
return nil
|
|
}
|