Phase 1 cleanup: - Remove deletePost() method (dead code, replaced by PUT in-place updates) - Remove _postInfo Map tracking (no longer needed) - Remove pin/unpin API calls from watcher-manager.js (incompatible with PUT updates) - Add JSDoc note on (edited) label limitation in _flushUpdate() - Add integration test: test/integration/poll-fallback.test.js - Fix addSession() lastOffset===0 falsy bug (0 was treated as 'no offset') - Fix pre-existing test failures: add lastOffset:0 where tests expect backlog reads - Fix pre-existing session-monitor test: create stub transcript files - Fix pre-existing status-formatter test: update indent check for blockquote format - Format plugin/ files with Prettier (pre-existing formatting drift)
118 lines
2.9 KiB
Go
118 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
)
|
|
|
|
const kvPrefix = "ls_session_"
|
|
|
|
// SessionData represents a tracked agent session.
|
|
type SessionData struct {
|
|
SessionKey string `json:"session_key"`
|
|
PostID string `json:"post_id"`
|
|
ChannelID string `json:"channel_id"`
|
|
RootID string `json:"root_id,omitempty"`
|
|
AgentID string `json:"agent_id"`
|
|
Status string `json:"status"` // active, done, error, interrupted
|
|
Lines []string `json:"lines"`
|
|
ElapsedMs int64 `json:"elapsed_ms"`
|
|
TokenCount int `json:"token_count"`
|
|
Children []SessionData `json:"children,omitempty"`
|
|
StartTimeMs int64 `json:"start_time_ms"`
|
|
}
|
|
|
|
// Store wraps Mattermost KV store operations for session persistence.
|
|
type Store struct {
|
|
api plugin.API
|
|
}
|
|
|
|
// NewStore creates a new Store instance.
|
|
func NewStore(api plugin.API) *Store {
|
|
return &Store{api: api}
|
|
}
|
|
|
|
// encodeKey URL-encodes a session key for safe KV storage.
|
|
func encodeKey(sessionKey string) string {
|
|
return kvPrefix + url.PathEscape(sessionKey)
|
|
}
|
|
|
|
// SaveSession stores a session in the KV store.
|
|
func (s *Store) SaveSession(sessionKey string, data SessionData) error {
|
|
b, err := json.Marshal(data)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal session: %w", err)
|
|
}
|
|
appErr := s.api.KVSet(encodeKey(sessionKey), b)
|
|
if appErr != nil {
|
|
return fmt.Errorf("kv set: %s", appErr.Error())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetSession retrieves a session from the KV store.
|
|
func (s *Store) GetSession(sessionKey string) (*SessionData, error) {
|
|
b, appErr := s.api.KVGet(encodeKey(sessionKey))
|
|
if appErr != nil {
|
|
return nil, fmt.Errorf("kv get: %s", appErr.Error())
|
|
}
|
|
if b == nil {
|
|
return nil, nil
|
|
}
|
|
var data SessionData
|
|
if err := json.Unmarshal(b, &data); err != nil {
|
|
return nil, fmt.Errorf("unmarshal session: %w", err)
|
|
}
|
|
return &data, nil
|
|
}
|
|
|
|
// DeleteSession removes a session from the KV store.
|
|
func (s *Store) DeleteSession(sessionKey string) error {
|
|
appErr := s.api.KVDelete(encodeKey(sessionKey))
|
|
if appErr != nil {
|
|
return fmt.Errorf("kv delete: %s", appErr.Error())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListActiveSessions returns all active sessions from the KV store.
|
|
func (s *Store) ListActiveSessions() ([]SessionData, error) {
|
|
var sessions []SessionData
|
|
page := 0
|
|
perPage := 100
|
|
|
|
for {
|
|
keys, appErr := s.api.KVList(page, perPage)
|
|
if appErr != nil {
|
|
return nil, fmt.Errorf("kv list: %s", appErr.Error())
|
|
}
|
|
if len(keys) == 0 {
|
|
break
|
|
}
|
|
|
|
for _, key := range keys {
|
|
if !strings.HasPrefix(key, kvPrefix) {
|
|
continue
|
|
}
|
|
b, getErr := s.api.KVGet(key)
|
|
if getErr != nil || b == nil {
|
|
continue
|
|
}
|
|
var data SessionData
|
|
if err := json.Unmarshal(b, &data); err != nil {
|
|
continue
|
|
}
|
|
if data.Status == "active" {
|
|
sessions = append(sessions, data)
|
|
}
|
|
}
|
|
page++
|
|
}
|
|
|
|
return sessions, nil
|
|
}
|