mirror of
https://github.com/lukaszraczylo/claude-mnemonic.git
synced 2026-06-05 23:03:55 +00:00
Add the statusline. Fix the installation.
This commit is contained in:
@@ -74,12 +74,16 @@ func (s *ObservationStore) StoreObservation(ctx context.Context, sdkSessionID, p
|
||||
|
||||
id, _ := result.LastInsertId()
|
||||
|
||||
// Cleanup old observations beyond the limit for this project
|
||||
// Cleanup old observations beyond the limit for this project (async to not block handler)
|
||||
if project != "" {
|
||||
deletedIDs, _ := s.CleanupOldObservations(ctx, project)
|
||||
if len(deletedIDs) > 0 && s.cleanupFunc != nil {
|
||||
s.cleanupFunc(ctx, deletedIDs)
|
||||
}
|
||||
go func(proj string) {
|
||||
cleanupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
deletedIDs, _ := s.CleanupOldObservations(cleanupCtx, proj)
|
||||
if len(deletedIDs) > 0 && s.cleanupFunc != nil {
|
||||
s.cleanupFunc(cleanupCtx, deletedIDs)
|
||||
}
|
||||
}(project)
|
||||
}
|
||||
|
||||
return id, nowEpoch, nil
|
||||
|
||||
@@ -51,11 +51,15 @@ func (s *PromptStore) SaveUserPromptWithMatches(ctx context.Context, claudeSessi
|
||||
|
||||
id, _ := result.LastInsertId()
|
||||
|
||||
// Cleanup old prompts beyond the global limit
|
||||
deletedIDs, _ := s.CleanupOldPrompts(ctx)
|
||||
if len(deletedIDs) > 0 && s.cleanupFunc != nil {
|
||||
s.cleanupFunc(ctx, deletedIDs)
|
||||
}
|
||||
// Cleanup old prompts beyond the global limit (async to not block handler)
|
||||
go func() {
|
||||
cleanupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
deletedIDs, _ := s.CleanupOldPrompts(cleanupCtx)
|
||||
if len(deletedIDs) > 0 && s.cleanupFunc != nil {
|
||||
s.cleanupFunc(cleanupCtx, deletedIDs)
|
||||
}
|
||||
}()
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
@@ -514,7 +514,7 @@ func (s *Service) handleGetStats(w http.ResponseWriter, r *http.Request) {
|
||||
retrievalStats := s.GetRetrievalStats()
|
||||
sessionsToday, _ := s.sessionStore.GetSessionsToday(r.Context())
|
||||
|
||||
writeJSON(w, map[string]interface{}{
|
||||
response := map[string]interface{}{
|
||||
"uptime": time.Since(s.startTime).String(),
|
||||
"activeSessions": s.sessionManager.GetActiveSessionCount(),
|
||||
"queueDepth": s.sessionManager.GetTotalQueueDepth(),
|
||||
@@ -522,7 +522,19 @@ func (s *Service) handleGetStats(w http.ResponseWriter, r *http.Request) {
|
||||
"connectedClients": s.sseBroadcaster.ClientCount(),
|
||||
"sessionsToday": sessionsToday,
|
||||
"retrieval": retrievalStats,
|
||||
})
|
||||
"ready": s.ready.Load(),
|
||||
}
|
||||
|
||||
// Include project-specific observation count if project is specified
|
||||
if project := r.URL.Query().Get("project"); project != "" {
|
||||
count, err := s.observationStore.GetObservationCount(r.Context(), project)
|
||||
if err == nil {
|
||||
response["projectObservations"] = count
|
||||
response["project"] = project
|
||||
}
|
||||
}
|
||||
|
||||
writeJSON(w, response)
|
||||
}
|
||||
|
||||
// handleGetRetrievalStats returns detailed retrieval statistics.
|
||||
|
||||
@@ -6,10 +6,17 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// WriteTimeout is the timeout for writing to SSE clients.
|
||||
// Prevents blocking on stale connections.
|
||||
WriteTimeout = 2 * time.Second
|
||||
)
|
||||
|
||||
// Client represents a connected SSE client.
|
||||
type Client struct {
|
||||
ID string
|
||||
@@ -101,6 +108,7 @@ func (b *Broadcaster) removeClientByID(id string) {
|
||||
}
|
||||
|
||||
// Broadcast sends a message to all connected clients.
|
||||
// Uses non-blocking writes with timeout to prevent stale connections from blocking.
|
||||
func (b *Broadcaster) Broadcast(data interface{}) {
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
@@ -117,30 +125,67 @@ func (b *Broadcaster) Broadcast(data interface{}) {
|
||||
}
|
||||
b.mu.RUnlock()
|
||||
|
||||
// Track dead clients for removal
|
||||
var deadClients []*Client
|
||||
if len(clients) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Use a channel to collect dead clients from concurrent writes
|
||||
deadClientsCh := make(chan string, len(clients))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, client := range clients {
|
||||
select {
|
||||
case <-client.Done:
|
||||
continue
|
||||
default:
|
||||
_, err := client.Writer.Write([]byte(message))
|
||||
if err != nil {
|
||||
log.Debug().
|
||||
Str("clientId", client.ID).
|
||||
Err(err).
|
||||
Msg("Failed to write to SSE client, marking for removal")
|
||||
deadClients = append(deadClients, client)
|
||||
continue
|
||||
}
|
||||
client.Flusher.Flush()
|
||||
wg.Add(1)
|
||||
go func(c *Client) {
|
||||
defer wg.Done()
|
||||
b.writeToClient(c, message, deadClientsCh)
|
||||
}(client)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove dead clients outside the iteration
|
||||
for _, client := range deadClients {
|
||||
b.removeClientByID(client.ID)
|
||||
// Wait for all writes to complete (with their individual timeouts)
|
||||
wg.Wait()
|
||||
close(deadClientsCh)
|
||||
|
||||
// Remove dead clients
|
||||
for clientID := range deadClientsCh {
|
||||
b.removeClientByID(clientID)
|
||||
}
|
||||
}
|
||||
|
||||
// writeToClient writes a message to a single client with timeout.
|
||||
func (b *Broadcaster) writeToClient(client *Client, message string, deadCh chan<- string) {
|
||||
// Use a timeout channel to prevent blocking on stale connections
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(done)
|
||||
_, err := client.Writer.Write([]byte(message))
|
||||
if err != nil {
|
||||
log.Debug().
|
||||
Str("clientId", client.ID).
|
||||
Err(err).
|
||||
Msg("Failed to write to SSE client, marking for removal")
|
||||
deadCh <- client.ID
|
||||
return
|
||||
}
|
||||
client.Flusher.Flush()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
// Write completed successfully
|
||||
case <-time.After(WriteTimeout):
|
||||
log.Warn().
|
||||
Str("clientId", client.ID).
|
||||
Dur("timeout", WriteTimeout).
|
||||
Msg("SSE write timed out, marking client for removal")
|
||||
deadCh <- client.ID
|
||||
case <-client.Done:
|
||||
// Client disconnected during write
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user