diff --git a/internal/agent/router.go b/internal/agent/router.go index 0521550e..575dda61 100644 --- a/internal/agent/router.go +++ b/internal/agent/router.go @@ -130,6 +130,16 @@ func (r *Router) ListInfo() []AgentInfo { return infos } +// IsRunning checks if a specific agent is currently running (cached in router). +func (r *Router) IsRunning(agentID string) bool { + r.mu.RLock() + defer r.mu.RUnlock() + if entry, ok := r.agents[agentID]; ok { + return entry.agent.IsRunning() + } + return false +} + // --- Active Run Tracking (matching TS chat-abort.ts) --- // ActiveRun tracks a running agent invocation so it can be aborted via chat.abort. diff --git a/internal/gateway/methods/agents.go b/internal/gateway/methods/agents.go index 643ee62a..9d0b3565 100644 --- a/internal/gateway/methods/agents.go +++ b/internal/gateway/methods/agents.go @@ -33,6 +33,19 @@ func NewAgentsMethods(agents *agent.Router, cfg *config.Config, cfgPath, workspa return &AgentsMethods{agents: agents, cfg: cfg, cfgPath: cfgPath, workspace: workspace, agentStore: agentStore, isManaged: isManaged, interceptor: interceptor} } +// isOwnerUser checks if the given user ID is in the configured owner IDs. +func (m *AgentsMethods) isOwnerUser(userID string) bool { + if userID == "" { + return false + } + for _, id := range m.cfg.Gateway.OwnerIDs { + if id == userID { + return true + } + } + return false +} + func (m *AgentsMethods) Register(router *gateway.MethodRouter) { router.Register(protocol.MethodAgent, m.handleAgent) router.Register(protocol.MethodAgentWait, m.handleAgentWait) @@ -93,7 +106,50 @@ func (m *AgentsMethods) handleAgentWait(_ context.Context, client *gateway.Clien })) } -func (m *AgentsMethods) handleList(_ context.Context, client *gateway.Client, req *protocol.RequestFrame) { +func (m *AgentsMethods) handleList(ctx context.Context, client *gateway.Client, req *protocol.RequestFrame) { + // In managed mode, query the store so ALL accessible agents appear (not just router-cached ones). + if m.isManaged && m.agentStore != nil { + userID := client.UserID() + if userID == "" { + client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, "user context required")) + return + } + + var agents []store.AgentData + var err error + if m.isOwnerUser(userID) { + agents, err = m.agentStore.List(ctx, "") + } else { + agents, err = m.agentStore.ListAccessible(ctx, userID) + } + if err != nil { + slog.Warn("agents.list: store query failed", "error", err) + client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInternal, "failed to list agents")) + return + } + + infos := make([]map[string]interface{}, 0, len(agents)) + for _, a := range agents { + if a.Status != store.AgentStatusActive { + continue + } + infos = append(infos, map[string]interface{}{ + "id": a.AgentKey, + "name": a.DisplayName, + "model": a.Model, + "provider": a.Provider, + "agentType": a.AgentType, + "status": a.Status, + "isRunning": m.agents.IsRunning(a.AgentKey), + }) + } + client.SendResponse(protocol.NewOKResponse(req.ID, map[string]interface{}{ + "agents": infos, + })) + return + } + + // Standalone mode: return router-cached agents. infos := m.agents.ListInfo() client.SendResponse(protocol.NewOKResponse(req.ID, map[string]interface{}{ "agents": infos, diff --git a/ui/web/src/components/chat/agent-selector.tsx b/ui/web/src/components/chat/agent-selector.tsx index f5704164..b40f85e5 100644 --- a/ui/web/src/components/chat/agent-selector.tsx +++ b/ui/web/src/components/chat/agent-selector.tsx @@ -1,8 +1,8 @@ import { useState, useEffect } from "react"; import { Bot, ChevronDown } from "lucide-react"; -import { useWs } from "@/hooks/use-ws"; -import { Methods } from "@/api/protocol"; -import type { AgentInfo } from "@/types/agent"; +import { useHttp } from "@/hooks/use-ws"; +import { useAuthStore } from "@/stores/use-auth-store"; +import type { AgentData } from "@/types/agent"; interface AgentSelectorProps { value: string; @@ -10,18 +10,23 @@ interface AgentSelectorProps { } export function AgentSelector({ value, onChange }: AgentSelectorProps) { - const ws = useWs(); - const [agents, setAgents] = useState([]); + const http = useHttp(); + const connected = useAuthStore((s) => s.connected); + const [agents, setAgents] = useState([]); const [open, setOpen] = useState(false); useEffect(() => { - if (!ws.isConnected) return; - ws.call<{ agents: AgentInfo[] }>(Methods.AGENTS_LIST) - .then((res) => setAgents(res.agents ?? [])) + if (!connected) return; + http + .get<{ agents: AgentData[] }>("/v1/agents") + .then((res) => { + const active = (res.agents ?? []).filter((a) => a.status === "active"); + setAgents(active); + }) .catch(() => {}); - }, [ws]); + }, [http, connected]); - const selected = agents.find((a) => a.id === value); + const selected = agents.find((a) => a.agent_key === value); return (
@@ -32,7 +37,7 @@ export function AgentSelector({ value, onChange }: AgentSelectorProps) { > - {selected?.name ?? (value || "Select agent")} + {selected?.display_name ?? selected?.agent_key ?? (value || "Select agent")} @@ -48,20 +53,22 @@ export function AgentSelector({ value, onChange }: AgentSelectorProps) { )} {agents.map((agent) => ( ))}