mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-29 12:27:33 +08:00
add phase-1 nodes control plane scaffold and register nodes tool
This commit is contained in:
89
pkg/nodes/manager.go
Normal file
89
pkg/nodes/manager.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Manager keeps paired node metadata and basic routing helpers.
|
||||
type Manager struct {
|
||||
mu sync.RWMutex
|
||||
nodes map[string]NodeInfo
|
||||
}
|
||||
|
||||
func NewManager() *Manager {
|
||||
return &Manager{nodes: map[string]NodeInfo{}}
|
||||
}
|
||||
|
||||
func (m *Manager) Upsert(info NodeInfo) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
info.LastSeenAt = time.Now().UTC()
|
||||
info.Online = true
|
||||
m.nodes[info.ID] = info
|
||||
}
|
||||
|
||||
func (m *Manager) MarkOffline(id string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if n, ok := m.nodes[id]; ok {
|
||||
n.Online = false
|
||||
m.nodes[id] = n
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Get(id string) (NodeInfo, bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
n, ok := m.nodes[id]
|
||||
return n, ok
|
||||
}
|
||||
|
||||
func (m *Manager) List() []NodeInfo {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
out := make([]NodeInfo, 0, len(m.nodes))
|
||||
for _, n := range m.nodes {
|
||||
out = append(out, n)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].LastSeenAt.After(out[j].LastSeenAt) })
|
||||
return out
|
||||
}
|
||||
|
||||
func (m *Manager) PickFor(action string) (NodeInfo, bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
for _, n := range m.nodes {
|
||||
if !n.Online {
|
||||
continue
|
||||
}
|
||||
switch action {
|
||||
case "run":
|
||||
if n.Capabilities.Run {
|
||||
return n, true
|
||||
}
|
||||
case "camera_snap", "camera_clip":
|
||||
if n.Capabilities.Camera {
|
||||
return n, true
|
||||
}
|
||||
case "screen_record":
|
||||
if n.Capabilities.Screen {
|
||||
return n, true
|
||||
}
|
||||
case "location_get":
|
||||
if n.Capabilities.Location {
|
||||
return n, true
|
||||
}
|
||||
case "canvas_snapshot", "canvas_action":
|
||||
if n.Capabilities.Canvas {
|
||||
return n, true
|
||||
}
|
||||
default:
|
||||
if n.Capabilities.Invoke {
|
||||
return n, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return NodeInfo{}, false
|
||||
}
|
||||
41
pkg/nodes/types.go
Normal file
41
pkg/nodes/types.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package nodes
|
||||
|
||||
import "time"
|
||||
|
||||
// Capability matrix reported by each node agent.
|
||||
type Capabilities struct {
|
||||
Run bool `json:"run"`
|
||||
Invoke bool `json:"invoke"`
|
||||
Camera bool `json:"camera"`
|
||||
Screen bool `json:"screen"`
|
||||
Location bool `json:"location"`
|
||||
Canvas bool `json:"canvas"`
|
||||
}
|
||||
|
||||
// NodeInfo is the runtime descriptor for cross-device scheduling.
|
||||
type NodeInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
OS string `json:"os,omitempty"`
|
||||
Arch string `json:"arch,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Capabilities Capabilities `json:"capabilities"`
|
||||
LastSeenAt time.Time `json:"last_seen_at"`
|
||||
Online bool `json:"online"`
|
||||
}
|
||||
|
||||
// Envelope for node commands.
|
||||
type Request struct {
|
||||
Action string `json:"action"`
|
||||
Node string `json:"node,omitempty"`
|
||||
Args map[string]interface{} `json:"args,omitempty"`
|
||||
}
|
||||
|
||||
// Envelope for node responses.
|
||||
type Response struct {
|
||||
OK bool `json:"ok"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Node string `json:"node,omitempty"`
|
||||
Action string `json:"action,omitempty"`
|
||||
Payload map[string]interface{} `json:"payload,omitempty"`
|
||||
}
|
||||
Reference in New Issue
Block a user