chore: initial commit (sources + docs, ignore build artifacts and secrets)
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user