diff options
| -rw-r--r-- | internal/executor/executor.go | 2 | ||||
| -rw-r--r-- | internal/storage/db.go | 12 | ||||
| -rw-r--r-- | web/app.js | 6 | ||||
| -rw-r--r-- | web/style.css | 5 |
4 files changed, 17 insertions, 8 deletions
diff --git a/internal/executor/executor.go b/internal/executor/executor.go index 9052bd7..b8979a1 100644 --- a/internal/executor/executor.go +++ b/internal/executor/executor.go @@ -626,6 +626,7 @@ type AgentStatusInfo struct { ActiveTasks int `json:"active_tasks"` RateLimited bool `json:"rate_limited"` Until *time.Time `json:"until,omitempty"` + Drained bool `json:"drained"` } // AgentStatuses returns the current status of all registered agents. @@ -638,6 +639,7 @@ func (p *Pool) AgentStatuses() []AgentStatusInfo { info := AgentStatusInfo{ Agent: agent, ActiveTasks: p.activePerAgent[agent], + Drained: p.drained[agent], } if deadline, ok := p.rateLimited[agent]; ok && now.Before(deadline) { info.RateLimited = true diff --git a/internal/storage/db.go b/internal/storage/db.go index 24a6cd3..ee5ee77 100644 --- a/internal/storage/db.go +++ b/internal/storage/db.go @@ -1031,12 +1031,12 @@ func (s *DB) SetSetting(key, value string) error { // AgentEvent records a rate-limit state change for an agent. type AgentEvent struct { - ID string - Agent string - Event string // "rate_limited" | "available" - Timestamp time.Time - Until *time.Time // non-nil for "rate_limited" events - Reason string // "transient" | "quota" + ID string `json:"id"` + Agent string `json:"agent"` + Event string `json:"event"` // "rate_limited" | "available" + Timestamp time.Time `json:"timestamp"` + Until *time.Time `json:"until,omitempty"` // non-nil for "rate_limited" events + Reason string `json:"reason"` // "transient" | "quota" } // RecordAgentEvent inserts an agent rate-limit event. @@ -2873,7 +2873,7 @@ function renderStatsPanel(tasks, executions, agentData = { agents: [], events: [ for (const ag of agents) { const card = document.createElement('div'); card.className = 'stats-agent-card'; - const statusClass = ag.rate_limited ? 'agent-rate-limited' : 'agent-available'; + const statusClass = ag.drained ? 'agent-drained' : ag.rate_limited ? 'agent-rate-limited' : 'agent-available'; card.classList.add(statusClass); const nameEl = document.createElement('span'); @@ -2882,7 +2882,9 @@ function renderStatsPanel(tasks, executions, agentData = { agents: [], events: [ const statusEl = document.createElement('span'); statusEl.className = 'stats-agent-status'; - if (ag.rate_limited && ag.until) { + if (ag.drained) { + statusEl.textContent = 'Drain locked — needs manual undrain'; + } else if (ag.rate_limited && ag.until) { const untilDate = new Date(ag.until); const minsLeft = Math.max(0, Math.round((untilDate - Date.now()) / 60000)); statusEl.textContent = `Rate limited — ${minsLeft}m remaining`; diff --git a/web/style.css b/web/style.css index 2bba8dc..1aa6627 100644 --- a/web/style.css +++ b/web/style.css @@ -1727,6 +1727,11 @@ dialog label select:focus { background: color-mix(in srgb, var(--state-failed) 8%, transparent); } +.stats-agent-card.agent-drained { + border-color: var(--state-cancelled); + background: color-mix(in srgb, var(--state-cancelled) 8%, transparent); +} + .stats-agent-name { font-weight: 600; font-size: 0.9rem; |
