fix(agent): refresh live status from AM in the agent LIST endpoint
GET /api/heicode/agents previously returned only the stored status, so a
freshly-deployed agent stayed "Pending"/"启动中" forever — the desktop client
polling the list never saw it go "running", and the web list only updated when
the user opened the detail panel (which polls /agents/{id}/status and was the
only path that refreshed from AM).
Now the list refreshes every non-terminal (non-"stopped") agent's live status
from AM before responding, concurrently under an 8s deadline. Per-user agents
are capped (≤8) so the fan-out is bounded and the list never hangs on a slow
AM. refreshAgentStatus now takes context.Context (callers pass the request ctx).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -256,6 +258,14 @@ func HeicodeListAgents(c *gin.Context) {
|
||||
agentError(c, "DEPLOYMENT_CONFLICT", "failed to list agents")
|
||||
return
|
||||
}
|
||||
// Refresh live status from AM for every non-terminal agent BEFORE returning,
|
||||
// so the list (and the desktop client that polls it) reflects reality without
|
||||
// needing the detail panel to be opened. Previously only the detail/status
|
||||
// endpoints refreshed, so a freshly-deployed agent stayed "Pending" in the
|
||||
// list forever. Bounded: per-user agent count is capped (≤8) and the refreshes
|
||||
// run concurrently under a short deadline, so the list never hangs on a slow AM.
|
||||
refreshAgentStatusBatch(c.Request.Context(), rows)
|
||||
|
||||
items := make([]gin.H, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
items = append(items, templateAgentResponse(row))
|
||||
@@ -263,9 +273,31 @@ func HeicodeListAgents(c *gin.Context) {
|
||||
common.ApiSuccess(c, gin.H{"items": items, "total": len(items)})
|
||||
}
|
||||
|
||||
// refreshAgentStatusBatch concurrently refreshes the live status of every
|
||||
// non-terminal agent in rows (best-effort) under a single bounded deadline.
|
||||
// Each row is updated in place + persisted by refreshAgentStatus.
|
||||
func refreshAgentStatusBatch(ctx context.Context, rows []model.AgentDeployment) {
|
||||
ctx, cancel := context.WithTimeout(ctx, 8*time.Second)
|
||||
defer cancel()
|
||||
var wg sync.WaitGroup
|
||||
for i := range rows {
|
||||
row := &rows[i]
|
||||
if strings.TrimSpace(row.RuntimeDeploymentID) == "" ||
|
||||
strings.EqualFold(strings.TrimSpace(row.Status), "stopped") {
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(r *model.AgentDeployment) {
|
||||
defer wg.Done()
|
||||
refreshAgentStatus(ctx, r)
|
||||
}(row)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// refreshAgentStatus best-effort pulls the live status from AM and persists it.
|
||||
// On any AM error it keeps the last-known status (never blocks the read).
|
||||
func refreshAgentStatus(c *gin.Context, row *model.AgentDeployment) {
|
||||
func refreshAgentStatus(ctx context.Context, row *model.AgentDeployment) {
|
||||
if strings.TrimSpace(row.RuntimeDeploymentID) == "" {
|
||||
return
|
||||
}
|
||||
@@ -275,7 +307,7 @@ func refreshAgentStatus(c *gin.Context, row *model.AgentDeployment) {
|
||||
if strings.EqualFold(strings.TrimSpace(row.Status), "stopped") {
|
||||
return
|
||||
}
|
||||
status, err := amGetAgentStatus(c.Request.Context(), row.RuntimeDeploymentID)
|
||||
status, err := amGetAgentStatus(ctx, row.RuntimeDeploymentID)
|
||||
if err != nil || strings.TrimSpace(status) == "" || status == row.Status {
|
||||
return
|
||||
}
|
||||
@@ -299,7 +331,7 @@ func HeicodeGetAgent(c *gin.Context) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
refreshAgentStatus(c, &row)
|
||||
refreshAgentStatus(c.Request.Context(), &row)
|
||||
common.ApiSuccess(c, templateAgentResponse(row))
|
||||
}
|
||||
|
||||
@@ -310,7 +342,7 @@ func HeicodeGetAgentStatus(c *gin.Context) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
refreshAgentStatus(c, &row)
|
||||
refreshAgentStatus(c.Request.Context(), &row)
|
||||
common.ApiSuccess(c, gin.H{
|
||||
"agent_id": row.DeploymentID,
|
||||
"status": row.Status,
|
||||
|
||||
Reference in New Issue
Block a user