100 lines
5.0 KiB
Go
100 lines
5.0 KiB
Go
package controlplane
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// AgentDeploymentStatus summarizes controller-owned LLM, Skill and MCP application state.
|
|
type AgentDeploymentStatus struct {
|
|
AgentID string `json:"agentID"`
|
|
LLMProfile string `json:"llmProfile"`
|
|
LLMStatus string `json:"llmStatus"`
|
|
SkillTotal int `json:"skillTotal"`
|
|
SkillApplied int `json:"skillApplied"`
|
|
SkillFailed int `json:"skillFailed"`
|
|
MCPStatus string `json:"mcpStatus"`
|
|
MCPError string `json:"mcpError"`
|
|
LastUpdatedAt string `json:"lastUpdatedAt"`
|
|
}
|
|
|
|
// EnableDesktopFilesystem deploys the intentionally narrow built-in filesystem MCP.
|
|
func (c *ControlPlane) EnableDesktopFilesystem(agentIDs []string) error {
|
|
if len(agentIDs) == 0 {
|
|
return errors.New("请选择至少一个 Agent")
|
|
}
|
|
now := time.Now().UTC().Format(time.RFC3339Nano)
|
|
for _, agentID := range agentIDs {
|
|
var exists int
|
|
if err := c.db.QueryRow("SELECT COUNT(*) FROM managed_agents WHERE agent_id=?", agentID).Scan(&exists); err != nil || exists != 1 {
|
|
return errors.New("目标 Agent 不存在")
|
|
}
|
|
if _, err := c.db.Exec("INSERT INTO agent_mcp_assignments(agent_id, tool_id, deployment_version, apply_status, last_error, updated_at) VALUES (?, 'desktop-filesystem', 1, 'pending', '', ?) ON CONFLICT(agent_id, tool_id) DO UPDATE SET deployment_version=agent_mcp_assignments.deployment_version + 1, apply_status='pending', last_error='', updated_at=excluded.updated_at", agentID, now); err != nil {
|
|
return err
|
|
}
|
|
c.recordAudit("mcp.deployment_requested", "controller", "agent:"+agentID+":desktop-filesystem", "pending", "已请求部署受控桌面文件工具。")
|
|
c.pushMCP(agentID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *ControlPlane) pushMCP(agentID string) {
|
|
client := c.clientFor(agentID)
|
|
if client == nil {
|
|
return
|
|
}
|
|
var version int
|
|
if err := c.db.QueryRow("SELECT deployment_version FROM agent_mcp_assignments WHERE agent_id=? AND tool_id='desktop-filesystem'", agentID).Scan(&version); err != nil {
|
|
return
|
|
}
|
|
if client.send(map[string]any{"type": "mcp_snapshot", "tools": []map[string]any{{"id": "desktop-filesystem", "deployment_version": version, "enabled": true, "scope": "desktop-directories"}}}) == nil {
|
|
_, _ = c.db.Exec("UPDATE agent_mcp_assignments SET apply_status='sent', updated_at=? WHERE agent_id=? AND tool_id='desktop-filesystem' AND deployment_version=?", time.Now().UTC().Format(time.RFC3339Nano), agentID, version)
|
|
c.recordAudit("mcp.snapshot_sent", "controller", "agent:"+agentID+":desktop-filesystem", "sent", "已通过认证 WSS 下发受控桌面文件工具。")
|
|
}
|
|
}
|
|
|
|
func (c *ControlPlane) applyMCPAck(agentID string, message map[string]any) {
|
|
toolID, _ := message["tool_id"].(string)
|
|
version, _ := message["deployment_version"].(float64)
|
|
status, _ := message["status"].(string)
|
|
errorText, _ := message["error"].(string)
|
|
if toolID != "desktop-filesystem" || version < 1 || (status != "applied" && status != "failed") {
|
|
return
|
|
}
|
|
result, _ := c.db.Exec("UPDATE agent_mcp_assignments SET apply_status=?, last_error=?, updated_at=? WHERE agent_id=? AND tool_id=? AND deployment_version=?", status, limit(sanitizeDisplayText(errorText), 1000), time.Now().UTC().Format(time.RFC3339Nano), agentID, toolID, int(version))
|
|
if changed, _ := result.RowsAffected(); changed == 0 {
|
|
return // Stale acknowledgement from an earlier deployment; preserve newer state.
|
|
}
|
|
c.recordAudit("mcp.deployment_ack", "agent:"+agentID, "agent:"+agentID+":desktop-filesystem", status, errorText)
|
|
}
|
|
|
|
// AgentDeploymentStatuses provides the Agent page with consolidated deployment state.
|
|
func (c *ControlPlane) AgentDeploymentStatuses() ([]AgentDeploymentStatus, error) {
|
|
rows, err := c.db.Query(`SELECT a.agent_id,
|
|
COALESCE(p.name, ''), COALESCE(l.apply_status, '未部署'),
|
|
COALESCE((SELECT COUNT(*) FROM agent_skill_assignments s WHERE s.agent_id = a.agent_id), 0),
|
|
COALESCE((SELECT COUNT(*) FROM agent_skill_assignments s WHERE s.agent_id = a.agent_id AND s.apply_status = 'applied'), 0),
|
|
COALESCE((SELECT COUNT(*) FROM agent_skill_assignments s WHERE s.agent_id = a.agent_id AND s.apply_status = 'failed'), 0),
|
|
COALESCE(m.apply_status, '未启用'), COALESCE(m.last_error, ''),
|
|
COALESCE(MAX(l.updated_at, m.updated_at), '')
|
|
FROM managed_agents a
|
|
LEFT JOIN agent_llm_assignments l ON l.agent_id = a.agent_id
|
|
LEFT JOIN llm_profiles p ON p.profile_id = l.profile_id
|
|
LEFT JOIN agent_mcp_assignments m ON m.agent_id = a.agent_id AND m.tool_id = 'desktop-filesystem'
|
|
GROUP BY a.agent_id, p.name, l.apply_status, m.apply_status, m.last_error`)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("无法读取 Agent 部署状态: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
statuses := make([]AgentDeploymentStatus, 0)
|
|
for rows.Next() {
|
|
var status AgentDeploymentStatus
|
|
if err := rows.Scan(&status.AgentID, &status.LLMProfile, &status.LLMStatus, &status.SkillTotal, &status.SkillApplied, &status.SkillFailed, &status.MCPStatus, &status.MCPError, &status.LastUpdatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
statuses = append(statuses, status)
|
|
}
|
|
return statuses, rows.Err()
|
|
}
|