mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-13 13:17:28 +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>`
|
||||
- 可在 `NodeInfo` 中配置 `token`,relay 会自动附加 `Authorization: Bearer <token>`
|
||||
- `nodes` 工具支持设备快捷参数:`facing`、`duration_ms`、`command`
|
||||
- 设备动作响应统一:`ok/code/error/payload`(code 示例:`ok` `unsupported_action` `transport_error`)
|
||||
|
||||
实现位置:
|
||||
- `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
|
||||
- `NodeInfo.token` is supported; relay automatically sets `Authorization: Bearer <token>`
|
||||
- `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:
|
||||
- `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 {
|
||||
switch req.Action {
|
||||
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 {
|
||||
parts := make([]string, 0, len(cmdRaw))
|
||||
for _, x := range cmdRaw {
|
||||
@@ -90,11 +90,23 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
|
||||
}
|
||||
payload["command"] = parts
|
||||
}
|
||||
return nodes.Response{OK: true, Node: "local", Action: req.Action, Payload: payload}
|
||||
case "camera_snap", "camera_clip", "screen_record", "screen_snapshot", "location_get", "canvas_snapshot", "canvas_action":
|
||||
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: payload}
|
||||
case "camera_snap":
|
||||
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:
|
||||
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}}
|
||||
|
||||
@@ -96,18 +96,18 @@ func actionHTTPPath(action string) string {
|
||||
|
||||
func (s *HTTPRelayTransport) Send(ctx context.Context, req Request) (Response, error) {
|
||||
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 {
|
||||
return resp, nil
|
||||
}
|
||||
n, ok := s.Manager.Get(req.Node)
|
||||
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), "/")
|
||||
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
|
||||
if client == nil {
|
||||
@@ -125,13 +125,13 @@ func (s *HTTPRelayTransport) Send(ctx context.Context, req Request) (Response, e
|
||||
}
|
||||
hresp, err := client.Do(hreq)
|
||||
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()
|
||||
payload, _ := io.ReadAll(io.LimitReader(hresp.Body, 1<<20))
|
||||
var resp Response
|
||||
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) == "" {
|
||||
resp.Node = req.Node
|
||||
@@ -139,5 +139,12 @@ func (s *HTTPRelayTransport) Send(ctx context.Context, req Request) (Response, e
|
||||
if strings.TrimSpace(resp.Action) == "" {
|
||||
resp.Action = req.Action
|
||||
}
|
||||
if strings.TrimSpace(resp.Code) == "" {
|
||||
if resp.OK {
|
||||
resp.Code = "ok"
|
||||
} else {
|
||||
resp.Code = "remote_error"
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ type Request struct {
|
||||
// Envelope for node responses.
|
||||
type Response struct {
|
||||
OK bool `json:"ok"`
|
||||
Code string `json:"code,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Node string `json:"node,omitempty"`
|
||||
Action string `json:"action,omitempty"`
|
||||
|
||||
Reference in New Issue
Block a user