feat(mcp): 用 MCP 服务替换内置 AI Agent (#319)

在现有 HTTP 服务挂载 /mcp,让外部 AI 经共享 API Key 调用原分辨率截图、批量键鼠、ATX/WOL 和虚拟介质八个工具。所有调用共用有界 FIFO 和全局限流,复用现有采集管线与硬件控制器。

移除内置模型调用、网页 Agent 面板及旧配置,设置页提供可持续查看的 Key、统一的地址/密钥复制样式。补齐 DEB 的 libudev1 运行依赖。

验证:583 项 Rust 库测试、前端生产构建、ARMv7 设备视觉操作与介质/WOL 实测,以及默认 Release 构建和 DEB 包校验通过。

Refs #319
This commit is contained in:
mofeng-git
2026-10-19 12:12:03 +08:00
parent 3f970ee834
commit 7ea2936436
50 changed files with 2910 additions and 3713 deletions

View File

@@ -120,6 +120,10 @@ enum QueuedHidEvent {
Mouse(MouseEvent),
Consumer(ConsumerEvent),
Reset,
Confirmed(
Box<QueuedHidEvent>,
tokio::sync::oneshot::Sender<Result<()>>,
),
}
pub struct HidController {
@@ -313,6 +317,36 @@ impl HidController {
self.enqueue_event(QueuedHidEvent::Consumer(event)).await
}
/// MCP uses acknowledged events so timing starts after the hardware send.
pub async fn send_keyboard_confirmed(&self, event: KeyboardEvent) -> Result<()> {
self.send_confirmed(QueuedHidEvent::Keyboard(event)).await
}
pub async fn send_mouse_confirmed(&self, event: MouseEvent) -> Result<()> {
self.send_confirmed(QueuedHidEvent::Mouse(event)).await
}
pub async fn reset_confirmed(&self) -> Result<()> {
self.send_confirmed(QueuedHidEvent::Reset).await
}
async fn send_confirmed(&self, event: QueuedHidEvent) -> Result<()> {
if !self.backend_available.load(Ordering::Acquire) {
return Err(AppError::ServiceUnavailable(
"HID backend not available".into(),
));
}
let (tx, rx) = tokio::sync::oneshot::channel();
self.enqueue_event(QueuedHidEvent::Confirmed(Box::new(event), tx))
.await?;
tokio::time::timeout(Duration::from_secs(3), rx)
.await
.map_err(|_| AppError::ServiceUnavailable("HID hardware send timed out".into()))?
.map_err(|_| {
AppError::ServiceUnavailable("HID event discarded before sending".into())
})?
}
pub async fn bluetooth_status(&self) -> Result<serde_json::Value> {
let backend = self
.backend
@@ -625,6 +659,18 @@ impl HidController {
}
}
#[cfg(test)]
pub(crate) async fn test_controller(backend: Arc<dyn HidBackend>) -> Arc<HidController> {
#[cfg(unix)]
let controller = Arc::new(HidController::new(HidBackendType::None, None));
#[cfg(not(unix))]
let controller = Arc::new(HidController::new(HidBackendType::None));
*controller.backend.write().await = Some(backend);
controller.sync_runtime_state_from_backend().await;
controller.start_event_worker().await;
controller
}
async fn apply_backend_runtime_state(
backend_type: &Arc<RwLock<HidBackendType>>,
runtime_state: &Arc<RwLock<HidRuntimeState>>,
@@ -645,35 +691,34 @@ async fn process_hid_event(
event: QueuedHidEvent,
backend: &Arc<RwLock<Option<Arc<dyn HidBackend>>>>,
) {
let backend_opt = backend.read().await.clone();
let backend = match backend_opt {
Some(b) => b,
None => return,
let (event, reply) = match event {
QueuedHidEvent::Confirmed(event, reply) => (*event, Some(reply)),
event => (event, None),
};
let backend_for_send = backend.clone();
let result = tokio::task::spawn_blocking(move || {
futures::executor::block_on(async move {
match event {
QueuedHidEvent::Keyboard(ev) => backend_for_send.send_keyboard(ev).await,
QueuedHidEvent::Mouse(ev) => backend_for_send.send_mouse(ev).await,
QueuedHidEvent::Consumer(ev) => backend_for_send.send_consumer(ev).await,
QueuedHidEvent::Reset => backend_for_send.reset().await,
}
let backend = backend.read().await.clone();
let result = match backend {
Some(backend) => tokio::task::spawn_blocking(move || {
futures::executor::block_on(async move {
match event {
QueuedHidEvent::Keyboard(ev) => backend.send_keyboard(ev).await,
QueuedHidEvent::Mouse(ev) => backend.send_mouse(ev).await,
QueuedHidEvent::Consumer(ev) => backend.send_consumer(ev).await,
QueuedHidEvent::Reset => backend.reset().await,
QueuedHidEvent::Confirmed(..) => unreachable!("nested confirmed HID event"),
}
})
})
})
.await;
let result = match result {
Ok(r) => r,
Err(_) => return,
.await
.unwrap_or_else(|_| Err(AppError::ServiceUnavailable("HID worker failed".into()))),
None => Err(AppError::ServiceUnavailable(
"HID backend not available".into(),
)),
};
match result {
Ok(_) => {}
Err(e) => {
warn!("HID event processing failed: {}", e);
}
if let Err(error) = &result {
warn!("HID event processing failed: {}", error);
}
if let Some(reply) = reply {
let _ = reply.send(result);
}
}