CUB-203: fix Grimm review blocking issues (PR #41)
Some checks failed
Dev Build / deploy-dev (pull_request) Blocked by required conditions
Dev Build / build-test (pull_request) Waiting to run
Build (Dev) / build-go-backend (pull_request) Failing after 0s
Build (Dev) / trigger-deploy (pull_request) Has been skipped
Build (Dev) / build-frontend (pull_request) Failing after 1s
openclaw/grimm-review All 11 findings resolved. Approved.
Some checks failed
Dev Build / deploy-dev (pull_request) Blocked by required conditions
Dev Build / build-test (pull_request) Waiting to run
Build (Dev) / build-go-backend (pull_request) Failing after 0s
Build (Dev) / trigger-deploy (pull_request) Has been skipped
Build (Dev) / build-frontend (pull_request) Failing after 1s
openclaw/grimm-review All 11 findings resolved. Approved.
🔴 readLoop race: replace WriteControl close with ctx-done goroutine that closes conn 🔴 duplicate event handlers: clear handlers map before re-registering on reconnect 🔴 sync.go CurrentTask abuse: add DisplayName field to UpdateAgentRequest, use it 🔴 sync.go newRole dead code: add Role field to UpdateAgentRequest, use it 🔴 events.go handlePresence DB/SSE inconsistency: pass LastActivityAt in update, don't mutate after DB 🔴 events.go handleAgentConfig DB/SSE inconsistency: use DisplayName/Role fields in update 🟠 Send() nil-conn panic: check conn != nil before WriteJSON 🟠 readLoop prompt ctx cancellation: fixed by item #1 🟠 backoff never resets: reset to initialBackoff after successful connectAndRun 🟠 MarkWSReady double-close race: use sync.Once in Client Extra json:"-" dead fields: removed from sessionChangedPayload, presencePayload, agentConfigPayload UpdateAgentRequest: added DisplayName, Role, LastActivityAt fields
This commit is contained in:
@@ -18,8 +18,7 @@ import (
|
||||
// ── Event payload types ──────────────────────────────────────────────────
|
||||
|
||||
// sessionChangedPayload represents a single session delta from a
|
||||
// sessions.changed event. Fields are optional; use json.RawMessage for
|
||||
// anything we don't strictly need.
|
||||
// sessions.changed event.
|
||||
type sessionChangedPayload struct {
|
||||
SessionKey string `json:"sessionKey"`
|
||||
AgentID string `json:"agentId"`
|
||||
@@ -30,26 +29,23 @@ type sessionChangedPayload struct {
|
||||
TaskProgress *int `json:"taskProgress,omitempty"`
|
||||
TaskElapsed string `json:"taskElapsed"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
Extra json.RawMessage `json:"-"` // ignored; prevents crash on unknown fields
|
||||
}
|
||||
|
||||
// presencePayload represents a device presence update event.
|
||||
type presencePayload struct {
|
||||
AgentID string `json:"agentId"`
|
||||
Connected *bool `json:"connected,omitempty"`
|
||||
LastActivityAt string `json:"lastActivityAt"`
|
||||
Extra json.RawMessage `json:"-"` // ignored
|
||||
AgentID string `json:"agentId"`
|
||||
Connected *bool `json:"connected,omitempty"`
|
||||
LastActivityAt string `json:"lastActivityAt"`
|
||||
}
|
||||
|
||||
// agentConfigPayload represents an agent configuration change event.
|
||||
type agentConfigPayload struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Role string `json:"role"`
|
||||
Model string `json:"model"`
|
||||
Channel string `json:"channel"`
|
||||
Metadata json.RawMessage `json:"metadata"`
|
||||
Extra json.RawMessage `json:"-"` // ignored
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Role string `json:"role"`
|
||||
Model string `json:"model"`
|
||||
Channel string `json:"channel"`
|
||||
Metadata json.RawMessage `json:"metadata"`
|
||||
}
|
||||
|
||||
// ── Handler registration ─────────────────────────────────────────────────
|
||||
@@ -57,6 +53,16 @@ type agentConfigPayload struct {
|
||||
// registerEventHandlers sets up all live event handlers on the WSClient.
|
||||
// Call this once after a successful handshake + initial sync.
|
||||
func (c *WSClient) registerEventHandlers() {
|
||||
if c.agents == nil || c.broker == nil {
|
||||
c.logger.Info("event handlers skipped (no repository or broker)")
|
||||
return
|
||||
}
|
||||
|
||||
// Clear existing handlers to prevent duplicates on reconnect
|
||||
c.mu.Lock()
|
||||
c.handlers = make(map[string][]eventHandler)
|
||||
c.mu.Unlock()
|
||||
|
||||
c.OnEvent("sessions.changed", c.handleSessionsChanged)
|
||||
c.OnEvent("presence", c.handlePresence)
|
||||
c.OnEvent("agent.config", c.handleAgentConfig)
|
||||
@@ -199,6 +205,11 @@ func (c *WSClient) handlePresence(payload json.RawMessage) {
|
||||
update.Status = &idle
|
||||
}
|
||||
|
||||
// Pass lastActivityAt from the event so DB and SSE stay consistent
|
||||
if p.LastActivityAt != "" {
|
||||
update.LastActivityAt = &p.LastActivityAt
|
||||
}
|
||||
|
||||
// Update DB first
|
||||
updated, err := c.agents.Update(ctx, p.AgentID, update)
|
||||
if err != nil {
|
||||
@@ -207,11 +218,6 @@ func (c *WSClient) handlePresence(payload json.RawMessage) {
|
||||
return
|
||||
}
|
||||
|
||||
// Use reported timestamp if available
|
||||
if p.LastActivityAt != "" {
|
||||
updated.LastActivity = p.LastActivityAt
|
||||
}
|
||||
|
||||
// Then broadcast
|
||||
c.broker.Broadcast("agent.status", updated)
|
||||
|
||||
@@ -243,10 +249,14 @@ func (c *WSClient) handleAgentConfig(payload json.RawMessage) {
|
||||
defer cancel()
|
||||
|
||||
// Build partial update with available fields.
|
||||
// Note: DisplayName and Role are not in UpdateAgentRequest currently,
|
||||
// but Channel is. We update what we can and note the gap.
|
||||
update := models.UpdateAgentRequest{}
|
||||
|
||||
if cfg.Name != "" {
|
||||
update.DisplayName = &cfg.Name
|
||||
}
|
||||
if cfg.Role != "" {
|
||||
update.Role = &cfg.Role
|
||||
}
|
||||
if cfg.Channel != "" {
|
||||
update.Channel = &cfg.Channel
|
||||
}
|
||||
@@ -259,14 +269,6 @@ func (c *WSClient) handleAgentConfig(payload json.RawMessage) {
|
||||
return
|
||||
}
|
||||
|
||||
// Apply display name from config if the repo returned the default
|
||||
if cfg.Name != "" {
|
||||
updated.DisplayName = cfg.Name
|
||||
}
|
||||
if cfg.Role != "" {
|
||||
updated.Role = cfg.Role
|
||||
}
|
||||
|
||||
// Then broadcast fleet snapshot
|
||||
allAgents, err := c.agents.List(ctx, "")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user