feat: 设备能力矩阵校验 + 设备主动上报事件通道

回应架构讨论: 外接设备各自声明能力, 且补齐设备→agent 单向推送缺口。

1. 能力矩阵 (registry.go):
   - capabilityTools 映射: caps 声明 → 可用工具
     screen/screensue/screensee, computeruse, clipboard(see/sue),
     camera/camerasue, speaker/speakeruse
   - SupportsTool 校验: screensee/computeruse/clipboard* 执行前检查
     目标设备是否声明对应能力, 未声明直接报错(不再下发到设备端才失败)
   - 兼容规则: 声明 cmd/cmdrun 等历史值 → 全能力;
     未声明任何已知能力 → 全能力(旧设备兼容);
     有已知能力声明则严格匹配

2. 设备主动上报事件 (WS op=event):
   - 设备可推 {op:event, type, detail/payload} 无需回执
   - 插件层 SetEventHandler 回调: 格式化为人类可读文本,
     经 SDK InjectInput 异步注入 agent(source=device/{id}, 回复路由回同通道)
   - 同设备同类型事件 10s 节流防传感器风暴
   - 场景: 摄像头识别未知人员驻留主动告警, agent 收到后自主处置

测试: 能力矩阵8场景 + 摄像头调screensee被拒 + 事件上报回调(含device_id回退)
This commit is contained in:
JianFeeeee
2026-08-21 19:58:37 +08:00
parent b6c1ef15d2
commit 4dcd3623cb
4 changed files with 262 additions and 4 deletions

View File

@ -9,6 +9,7 @@ import (
"log"
"net/http"
"strings"
"sync"
"time"
"gitcode.com/JianFeeeee/HomeAgent/internal/plugin"
@ -115,6 +116,49 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
log.Printf("[remotedevice] register devicectl channel: %v", err)
}
// ---- 设备主动上报事件 → agent 注入 ----------------
// 摄像头发现异常/传感器报警等场景:设备经 WS op=event 上报,
// 插件将其格式化为文本经 SDK InjectText 异步注入 agent(source=device/{id},
// 回复路由回 device/{id} 通道),同时发 EventBus 供 WebUI 展示。
// 节流:同设备同类型事件 10s 内去重,防传感器风暴。
lastEventAt := map[string]time.Time{}
var eventMu sync.Mutex
p.registry.SetEventHandler(func(deviceID string, msg map[string]interface{}) {
evtType, _ := msg["type"].(string)
if evtType == "" {
evtType = "unknown"
}
key := deviceID + "|" + evtType
eventMu.Lock()
if last, ok := lastEventAt[key]; ok && time.Since(last) < 10*time.Second {
eventMu.Unlock()
log.Printf("[remotedevice] event throttled: %s from %s", evtType, deviceID)
return
}
lastEventAt[key] = time.Now()
eventMu.Unlock()
// 组装人类可读的事件文本(agent 可直接理解)
detail, _ := msg["detail"].(string)
if detail == "" {
if d, ok := msg["payload"].(map[string]interface{}); ok {
b, _ := json.Marshal(d)
detail = string(b)
}
}
text := fmt.Sprintf("【设备事件上报】设备 %s 触发事件 %s", deviceID, evtType)
if detail != "" {
text += ":" + detail
}
text += "。请关注此事件并按需处理(如通知用户、调用相关工具核实)。"
log.Printf("[remotedevice] event from %s: %s", deviceID, evtType)
if p.sdk != nil {
// 异步注入:不阻塞 WS 读循环;回复路由回 device/{id} 输出通道
p.sdk.InjectInput("device/"+deviceID, "device/"+deviceID, "text", map[string]interface{}{"content": text})
}
})
// ---- REST 管理面 + WS 设备通道 ----------------
p.registerRoutes()
p.server = &http.Server{Addr: p.addr, Handler: p.mux}