mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-08 03:57:30 +08:00
standardize device operation responses with codes and richer local simulations
This commit is contained in:
@@ -48,6 +48,7 @@
|
|||||||
- 可在 `gateway.token` 配置网关注册令牌;子节点注册/续租需带 `Authorization: Bearer <token>`
|
- 可在 `gateway.token` 配置网关注册令牌;子节点注册/续租需带 `Authorization: Bearer <token>`
|
||||||
- 可在 `NodeInfo` 中配置 `token`,relay 会自动附加 `Authorization: Bearer <token>`
|
- 可在 `NodeInfo` 中配置 `token`,relay 会自动附加 `Authorization: Bearer <token>`
|
||||||
- `nodes` 工具支持设备快捷参数:`facing`、`duration_ms`、`command`
|
- `nodes` 工具支持设备快捷参数:`facing`、`duration_ms`、`command`
|
||||||
|
- 设备动作响应统一:`ok/code/error/payload`(code 示例:`ok` `unsupported_action` `transport_error`)
|
||||||
|
|
||||||
实现位置:
|
实现位置:
|
||||||
- `pkg/nodes/types.go`
|
- `pkg/nodes/types.go`
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ A `nodes` tool control-plane PoC is now available:
|
|||||||
- configure `gateway.token` as registration token; child nodes must send `Authorization: Bearer <token>` for register/heartbeat
|
- configure `gateway.token` as registration token; child nodes must send `Authorization: Bearer <token>` for register/heartbeat
|
||||||
- `NodeInfo.token` is supported; relay automatically sets `Authorization: Bearer <token>`
|
- `NodeInfo.token` is supported; relay automatically sets `Authorization: Bearer <token>`
|
||||||
- `nodes` tool supports device shortcuts: `facing`, `duration_ms`, `command`
|
- `nodes` tool supports device shortcuts: `facing`, `duration_ms`, `command`
|
||||||
|
- unified device response envelope: `ok/code/error/payload` (code examples: `ok`, `unsupported_action`, `transport_error`)
|
||||||
|
|
||||||
Implementation:
|
Implementation:
|
||||||
- `pkg/nodes/types.go`
|
- `pkg/nodes/types.go`
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
|
|||||||
nodesManager.RegisterHandler("local", func(req nodes.Request) nodes.Response {
|
nodesManager.RegisterHandler("local", func(req nodes.Request) nodes.Response {
|
||||||
switch req.Action {
|
switch req.Action {
|
||||||
case "run":
|
case "run":
|
||||||
payload := map[string]interface{}{"transport": "relay-local"}
|
payload := map[string]interface{}{"transport": "relay-local", "simulated": true}
|
||||||
if cmdRaw, ok := req.Args["command"].([]interface{}); ok && len(cmdRaw) > 0 {
|
if cmdRaw, ok := req.Args["command"].([]interface{}); ok && len(cmdRaw) > 0 {
|
||||||
parts := make([]string, 0, len(cmdRaw))
|
parts := make([]string, 0, len(cmdRaw))
|
||||||
for _, x := range cmdRaw {
|
for _, x := range cmdRaw {
|
||||||
@@ -90,11 +90,23 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
|
|||||||
}
|
}
|
||||||
payload["command"] = parts
|
payload["command"] = parts
|
||||||
}
|
}
|
||||||
return nodes.Response{OK: true, Node: "local", Action: req.Action, Payload: payload}
|
return nodes.Response{OK: true, Code: "ok", Node: "local", Action: req.Action, Payload: payload}
|
||||||
case "camera_snap", "camera_clip", "screen_record", "screen_snapshot", "location_get", "canvas_snapshot", "canvas_action":
|
case "camera_snap":
|
||||||
return nodes.Response{OK: true, Node: "local", Action: req.Action, Payload: map[string]interface{}{"transport": "relay-local", "simulated": true, "args": req.Args}}
|
return nodes.Response{OK: true, Code: "ok", Node: "local", Action: req.Action, Payload: map[string]interface{}{"transport": "relay-local", "media_type": "image", "facing": req.Args["facing"], "simulated": true}}
|
||||||
|
case "camera_clip":
|
||||||
|
return nodes.Response{OK: true, Code: "ok", Node: "local", Action: req.Action, Payload: map[string]interface{}{"transport": "relay-local", "media_type": "video", "duration_ms": req.Args["duration_ms"], "simulated": true}}
|
||||||
|
case "screen_snapshot":
|
||||||
|
return nodes.Response{OK: true, Code: "ok", Node: "local", Action: req.Action, Payload: map[string]interface{}{"transport": "relay-local", "media_type": "image", "simulated": true}}
|
||||||
|
case "screen_record":
|
||||||
|
return nodes.Response{OK: true, Code: "ok", Node: "local", Action: req.Action, Payload: map[string]interface{}{"transport": "relay-local", "media_type": "video", "duration_ms": req.Args["duration_ms"], "simulated": true}}
|
||||||
|
case "location_get":
|
||||||
|
return nodes.Response{OK: true, Code: "ok", Node: "local", Action: req.Action, Payload: map[string]interface{}{"transport": "relay-local", "lat": 0.0, "lng": 0.0, "accuracy": "simulated"}}
|
||||||
|
case "canvas_snapshot":
|
||||||
|
return nodes.Response{OK: true, Code: "ok", Node: "local", Action: req.Action, Payload: map[string]interface{}{"transport": "relay-local", "image": "data:image/png;base64,<simulated>", "simulated": true}}
|
||||||
|
case "canvas_action":
|
||||||
|
return nodes.Response{OK: true, Code: "ok", Node: "local", Action: req.Action, Payload: map[string]interface{}{"transport": "relay-local", "applied": true, "simulated": true, "args": req.Args}}
|
||||||
default:
|
default:
|
||||||
return nodes.Response{OK: true, Node: "local", Action: req.Action, Payload: map[string]interface{}{"echo": req.Args, "transport": "relay-local"}}
|
return nodes.Response{OK: false, Code: "unsupported_action", Node: "local", Action: req.Action, Error: "unsupported local simulated action"}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
nodesRouter := &nodes.Router{P2P: &nodes.StubP2PTransport{}, Relay: &nodes.HTTPRelayTransport{Manager: nodesManager}}
|
nodesRouter := &nodes.Router{P2P: &nodes.StubP2PTransport{}, Relay: &nodes.HTTPRelayTransport{Manager: nodesManager}}
|
||||||
|
|||||||
@@ -96,18 +96,18 @@ func actionHTTPPath(action string) string {
|
|||||||
|
|
||||||
func (s *HTTPRelayTransport) Send(ctx context.Context, req Request) (Response, error) {
|
func (s *HTTPRelayTransport) Send(ctx context.Context, req Request) (Response, error) {
|
||||||
if s.Manager == nil {
|
if s.Manager == nil {
|
||||||
return Response{OK: false, Node: req.Node, Action: req.Action, Error: "relay manager not configured"}, nil
|
return Response{OK: false, Code: "relay_unavailable", Node: req.Node, Action: req.Action, Error: "relay manager not configured"}, nil
|
||||||
}
|
}
|
||||||
if resp, ok := s.Manager.Invoke(req); ok {
|
if resp, ok := s.Manager.Invoke(req); ok {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
n, ok := s.Manager.Get(req.Node)
|
n, ok := s.Manager.Get(req.Node)
|
||||||
if !ok {
|
if !ok {
|
||||||
return Response{OK: false, Node: req.Node, Action: req.Action, Error: "node not found"}, nil
|
return Response{OK: false, Code: "node_not_found", Node: req.Node, Action: req.Action, Error: "node not found"}, nil
|
||||||
}
|
}
|
||||||
endpoint := strings.TrimRight(strings.TrimSpace(n.Endpoint), "/")
|
endpoint := strings.TrimRight(strings.TrimSpace(n.Endpoint), "/")
|
||||||
if endpoint == "" {
|
if endpoint == "" {
|
||||||
return Response{OK: false, Node: req.Node, Action: req.Action, Error: "node endpoint not configured"}, nil
|
return Response{OK: false, Code: "endpoint_missing", Node: req.Node, Action: req.Action, Error: "node endpoint not configured"}, nil
|
||||||
}
|
}
|
||||||
client := s.Client
|
client := s.Client
|
||||||
if client == nil {
|
if client == nil {
|
||||||
@@ -125,13 +125,13 @@ func (s *HTTPRelayTransport) Send(ctx context.Context, req Request) (Response, e
|
|||||||
}
|
}
|
||||||
hresp, err := client.Do(hreq)
|
hresp, err := client.Do(hreq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Response{OK: false, Node: req.Node, Action: req.Action, Error: err.Error()}, nil
|
return Response{OK: false, Code: "transport_error", Node: req.Node, Action: req.Action, Error: err.Error()}, nil
|
||||||
}
|
}
|
||||||
defer hresp.Body.Close()
|
defer hresp.Body.Close()
|
||||||
payload, _ := io.ReadAll(io.LimitReader(hresp.Body, 1<<20))
|
payload, _ := io.ReadAll(io.LimitReader(hresp.Body, 1<<20))
|
||||||
var resp Response
|
var resp Response
|
||||||
if err := json.Unmarshal(payload, &resp); err != nil {
|
if err := json.Unmarshal(payload, &resp); err != nil {
|
||||||
return Response{OK: false, Node: req.Node, Action: req.Action, Error: fmt.Sprintf("invalid node response: %s", strings.TrimSpace(string(payload)))}, nil
|
return Response{OK: false, Code: "invalid_response", Node: req.Node, Action: req.Action, Error: fmt.Sprintf("invalid node response: %s", strings.TrimSpace(string(payload)))}, nil
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(resp.Node) == "" {
|
if strings.TrimSpace(resp.Node) == "" {
|
||||||
resp.Node = req.Node
|
resp.Node = req.Node
|
||||||
@@ -139,5 +139,12 @@ func (s *HTTPRelayTransport) Send(ctx context.Context, req Request) (Response, e
|
|||||||
if strings.TrimSpace(resp.Action) == "" {
|
if strings.TrimSpace(resp.Action) == "" {
|
||||||
resp.Action = req.Action
|
resp.Action = req.Action
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(resp.Code) == "" {
|
||||||
|
if resp.OK {
|
||||||
|
resp.Code = "ok"
|
||||||
|
} else {
|
||||||
|
resp.Code = "remote_error"
|
||||||
|
}
|
||||||
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ type Request struct {
|
|||||||
// Envelope for node responses.
|
// Envelope for node responses.
|
||||||
type Response struct {
|
type Response struct {
|
||||||
OK bool `json:"ok"`
|
OK bool `json:"ok"`
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
Node string `json:"node,omitempty"`
|
Node string `json:"node,omitempty"`
|
||||||
Action string `json:"action,omitempty"`
|
Action string `json:"action,omitempty"`
|
||||||
|
|||||||
Reference in New Issue
Block a user