LibreFang 桌面应用
LibreFang 桌面应用是基于 Tauri 2.0 构建的原生桌面封装,将整个 LibreFang Agent OS 打包为一个可安装的应用程序。用户无需运行 CLI 守护进程再打开浏览器,而是直接获得一个原生窗口,具备系统托盘集成、操作系统通知和单实例强制等功能——底层驱动的内核和 API 服务器与无头部署模式完全相同。
Crate: librefang-desktop
Identifier: ai.librefang.desktop
Product name: LibreFang
架构
桌面应用采用内嵌服务器模式:
+-------------------------------------------+
| Tauri 2.0 Process |
| |
| +-----------+ +--------------------+ |
| | Main | | Background Thread | |
| | Thread | | ("librefang-server")| |
| | | | | |
| | WebView | | tokio runtime | |
| | Window |--->| axum API server | |
| | (main) | | channel bridges | |
| | | | background agents | |
| | System | | | |
| | Tray | | LibreFang Kernel | |
| +-----------+ +--------------------+ |
| | | |
| | http://127.0.0.1:{port} |
| +------------------------------------
+-------------------------------------------+
启动流程
- 初始化日志追踪 -- 配置
tracing_subscriber,使用RUST_LOG环境变量控制日志级别,默认值为librefang=info,tauri=info。 - 内核启动 --
LibreFangKernel::boot(None)加载默认配置(来自config.toml或内置默认值),使用Arc包装。调用set_self_handle()以启用内核的自引用操作。 - 端口绑定 -- 在主线程上使用
std::net::TcpListener绑定到127.0.0.1:0,由操作系统分配一个随机可用端口。这确保在创建任何窗口之前就已获知端口号。 - 服务器线程 -- 生成一个名为
"librefang-server"的专用操作系统线程。该线程创建自己的tokio::runtime::Builder::new_multi_thread()运行时,并执行:kernel.start_background_agents()-- 心跳监控、自主 Agent 等。run_embedded_server()-- 通过librefang_api::server::build_router()构建 axum 路由器,将std::net::TcpListener转换为tokio::net::TcpListener,并以优雅关闭模式运行服务。
- Tauri 应用 -- 组装 Tauri builder,包含插件、托管状态、IPC 命令、系统托盘,以及指向
http://127.0.0.1:{port}的 WebView 窗口。 - 事件循环 -- Tauri 运行原生事件循环。退出时调用
server_handle.shutdown()停止内嵌服务器和内核。
ServerHandle
ServerHandle 结构体(定义在 src/server.rs 中)管理内嵌服务器的生命周期:
pub struct ServerHandle {
pub port: u16,
pub kernel: Arc<LibreFangKernel>,
shutdown_tx: watch::Sender<bool>,
server_thread: Option<std::thread::JoinHandle<()>>,
}
port-- 内嵌服务器监听的端口。kernel-- 内核的共享引用,Tauri 应用也会使用它来处理 IPC 命令和通知。shutdown_tx-- 一个tokio::sync::watch通道。发送true会触发 axum 服务器的优雅关闭。server_thread-- 后台线程的 join handle。shutdown()方法会 join 该线程以确保干净终止。
调用 shutdown() 会发送关闭信号、join 后台线程,并调用 kernel.shutdown()。Drop 实现会作为尽力而为的兜底措施发送关闭信号,但不会阻塞等待线程 join。
优雅关闭
axum 服务器通过 with_graceful_shutdown() 连接到 watch 通道:
let server = axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>())
.with_graceful_shutdown(async move {
let _ = shutdown_rx.wait_for(|v| *v).await;
});
服务器关闭后,通道桥接(Telegram、Slack 等)通过 bridge.stop().await 停止。
功能
连接屏
桌面 app 无法连到 daemon 时(首次启动、或捆绑的 daemon 起不来),现在会渲染一个专门的连接屏,而不是白屏错误。屏幕提供:
- "重试" — 重新尝试连接。
- "在浏览器打开" — 嵌入 WebView 是问题时回退到 daemon 自带的 web UI。
- "卸载" — 在 app 内完整移除,daemon 坏到从菜单栏都点不到时有用。
连接屏作为嵌入资源打包,用自定义 URI scheme(librefang://connection)渲染而不是经 daemon HTTP — 所以 daemon 停止或 IPC 桥断开时也能跑。Windows / Linux / macOS 走同一路径。
系统托盘
系统托盘(定义在 src/tray.rs 中)提供快捷操作,无需打开主窗口:
| 菜单项 | 行为 |
|---|---|
| Show Window | 对主 WebView 窗口调用 show()、unminimize() 和 set_focus() |
| Open in Browser | 从托管的 PortState 读取端口,在默认浏览器中打开 http://127.0.0.1:{port} |
| Agents: N running | 禁用(仅信息展示)——显示当前 Agent 数量 |
| Status: Running (uptime) | 禁用(仅信息展示)——以人类可读格式显示运行时间 |
| Launch at Login | 复选框——通过 tauri-plugin-autostart 切换操作系统级别的开机自启 |
| Check for Updates... | 检查更新、下载、安装并在有可用更新时重启。通过通知显示进度/成功/失败状态 |
| Open Config Directory | 在操作系统文件管理器中打开 ~/.librefang/ |
| Quit LibreFang | 记录退出事件并调用 app.exit(0) |
托盘提示文本为 "LibreFang Agent OS"。
左键单击托盘图标会显示主窗口(与 "Show Window" 菜单项效果相同)。此功能通过 on_tray_icon_event 监听 MouseButton::Left 配合 MouseButtonState::Up 实现。
单实例强制
在桌面平台上,tauri-plugin-single-instance 防止同时运行多个 LibreFang 实例。当第二个实例尝试启动时,现有实例的主窗口会被显示、取消最小化并获得焦点:
#[cfg(desktop)]
{
builder = builder.plugin(tauri_plugin_single_instance::init(
|app, _args, _cwd| {
if let Some(w) = app.get_webview_window("main") {
let _ = w.show();
let _ = w.unminimize();
let _ = w.set_focus();
}
},
));
}
关闭时最小化到托盘
关闭窗口不会退出应用程序。窗口会被隐藏,关闭事件会被阻止:
.on_window_event(|window, event| {
#[cfg(desktop)]
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
let _ = window.hide();
api.prevent_close();
}
})
要真正退出应用,请使用系统托盘菜单中的 "Quit LibreFang" 选项。
原生操作系统通知
应用订阅内核的事件总线,并使用 tauri-plugin-notification 将关键事件转发为原生桌面通知:
| 事件 | 通知标题 | 内容 |
|---|---|---|
LifecycleEvent::Crashed | "Agent Crashed" | Agent {id} crashed: {error} |
LifecycleEvent::Spawned | "Agent Started" | Agent "{name}" is now running |
SystemEvent::HealthCheckFailed | "Health Check Failed" | Agent {id} unresponsive for {secs}s |
其他所有事件均被静默跳过。通知监听器作为通过 tauri::async_runtime::spawn 生成的异步任务运行,并能优雅地处理广播延迟(记录警告后继续运行)。
IPC 命令
注册了十一个 Tauri IPC 命令,可通过 WebView 前端的 invoke() 调用:
get_port
返回内嵌服务器监听的端口号(u16)。
// Frontend usage
const port: number = await invoke("get_port");
get_status
返回包含运行时状态的 JSON 对象:
{
"status": "running",
"port": 8042,
"agents": 5,
"uptime_secs": 3600
}
agents-- 从kernel.registry.list()获取的已注册 Agent 数量。uptime_secs-- 自内核状态初始化(启动时通过Instant::now()记录)以来经过的秒数。
get_agent_count
以简单整数形式返回已注册 Agent 的数量(usize)。
const count: number = await invoke("get_agent_count");
import_agent_toml
打开原生文件选择器用于选择 .toml 文件。验证所选文件是否为有效的 AgentManifest,将其复制到 ~/.librefang/workspaces/agents/{name}/agent.toml,并启动该 Agent。成功时返回 Agent 名称。
import_skill_file
打开原生文件选择器用于选择技能文件(.md、.toml、.py、.js、.wasm)。将文件复制到 ~/.librefang/skills/ 并触发技能注册表的热重载。
get_autostart / set_autostart
检查或切换 LibreFang 是否在操作系统登录时自动启动。使用 tauri-plugin-autostart(macOS 上使用 launchd,Windows 上使用注册表,Linux 上使用 systemd)。
check_for_updates
检查可用更新但不安装。返回一个 UpdateInfo 对象:
{ "available": true, "version": "0.2.0", "body": "Release notes..." }
install_update
下载并安装最新更新,然后重启应用。成功时命令不会返回(应用会重启)。失败时返回错误字符串。
await invoke("install_update"); // App restarts if update succeeds
open_config_dir / open_logs_dir
在操作系统文件管理器中打开 ~/.librefang/ 或 ~/.librefang/logs/。
窗口配置
主窗口在 setup 闭包中以编程方式创建(不通过 tauri.conf.json,该文件声明了空的 windows: [] 数组):
| 属性 | 值 |
|---|---|
| Window label | "main" |
| Title | "LibreFang" |
| URL | http://127.0.0.1:{port}(外部地址) |
| Inner size | 1280 x 800 |
| Minimum inner size | 800 x 600 |
| Position | 居中 |
窗口使用 WebviewUrl::External(...) 而非打包的前端资源,因为 WebView 渲染的是 axum 提供的 UI。
自动更新
应用在启动 10 秒后检查更新。如有可用更新,会自动下载、安装并重启应用。用户也可通过系统托盘手动触发检查。
流程:
- 启动检查(10 秒延迟) →
check_for_update()→ 如有可用更新 → 通知用户 →download_and_install_update()→ 应用重启 - 托盘 "Check for Updates" → 相同流程,安装失败时显示失败通知
配置(在 tauri.conf.json 中):
plugins.updater.pubkey-- Ed25519 公钥(必须与签名私钥匹配)plugins.updater.endpoints-- 指向latest.json的 URL(托管在 GitHub Releases 上)plugins.updater.windows.installMode--"passive"(无完整 UI 的安装模式)
签名: 每个发布包都使用 TAURI_SIGNING_PRIVATE_KEY(GitHub Secret)签名。tauri-action 生成的 latest.json 包含各平台的下载 URL 和签名。
详见 Production Checklist 了解密钥生成和设置说明。
CSP
tauri.conf.json 配置了内容安全策略,允许连接到本地内嵌服务器:
default-src 'self' http://127.0.0.1:* ws://127.0.0.1:*;
img-src 'self' data: http://127.0.0.1:*;
style-src 'self' 'unsafe-inline';
script-src 'self' 'unsafe-inline'
这允许 WebView 从本地 API 服务器加载内容,同时阻止外部资源加载。axum API 服务器还提供额外的安全头中间件。
构建
前置条件
- Rust(stable 工具链)
- Tauri CLI v2:
cargo install tauri-cli --version "^2" - 平台特定依赖:
- Windows: WebView2(Windows 10/11 已内置)、Visual Studio Build Tools
- macOS: Xcode Command Line Tools
- Linux:
libwebkit2gtk-4.1-dev、libappindicator3-dev、librsvg2-dev、libssl-dev、build-essential
React Dashboard
内嵌 Web UI 由 axum API 服务器提供服务,包含两套并存的前端实现:
- 旧版 Alpine.js dashboard -- 从
crates/librefang-api/static/以单个 HTML 文件形式提供。始终可用,无需构建步骤。 - React dashboard -- 位于
crates/librefang-api/dashboard/。基于 Vite + React + TanStack Router + TanStack Query 构建。构建产物输出到crates/librefang-api/static/react/(已 gitignore);CI 自动构建并上传到 release assets。
修改前端代码后,重新构建 React dashboard:
cd crates/librefang-api/dashboard-react
npm install
npm run build
输出写入 ../static/react/(已 gitignore)。本地构建时 include_dir! 会嵌入到二进制;生产环境中,daemon 启动时从 release assets 下载 dashboard 资源。
开发模式
cd crates/librefang-desktop
cargo tauri dev
这会以热重载模式启动应用。在 debug 构建中,控制台窗口可见以便查看日志追踪输出。
生产构建
cd crates/librefang-desktop
cargo tauri build
这会生成平台特定的安装包:
- Windows:
.msi和.exe(NSIS)安装包 - macOS:
.dmg和.app应用包 - Linux:
.deb、.rpm和.AppImage
发布二进制文件通过以下代码在 Windows 上隐藏控制台窗口:
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
打包配置
来自 tauri.conf.json:
{
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/icon.png",
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png"
]
}
}
"targets": "all" 设置会为当前平台生成所有可用的包格式。图标提供了多种分辨率,另有 icon.ico 供 Windows 使用。
插件
| 插件 | 版本 | 用途 |
|---|---|---|
tauri-plugin-notification | 2 | 为内核事件和更新进度提供原生操作系统通知 |
tauri-plugin-shell | 2 | 从 WebView 访问 Shell/进程 |
tauri-plugin-dialog | 2 | Agent/技能导入的原生文件选择器 |
tauri-plugin-single-instance | 2 | 防止多实例运行(仅桌面端) |
tauri-plugin-autostart | 2 | 操作系统登录时自动启动(仅桌面端) |
tauri-plugin-updater | 2 | 从 GitHub Releases 进行签名自动更新(仅桌面端) |
tauri-plugin-global-shortcut | 2 | Ctrl+Shift+O/N/C 快捷键(仅桌面端) |
能力声明
默认能力集(定义在 capabilities/default.json 中)授予以下权限:
{
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:default",
"notification:default",
"shell:default",
"dialog:default",
"global-shortcut:allow-register",
"global-shortcut:allow-unregister",
"global-shortcut:allow-is-registered",
"autostart:default",
"updater:default"
]
}
仅 "main" 窗口获得这些权限。
移动端支持
代码库包含条件编译守卫以支持移动平台:
- 入口点:
run()函数标注了#[cfg_attr(mobile, tauri::mobile_entry_point)],允许 Tauri 将其用作移动端入口点。 - 仅桌面端功能:系统托盘设置、单实例强制和关闭时最小化到托盘都通过
#[cfg(desktop)]门控,在移动端目标编译时被排除。 - 移动端目标:Tauri 2.0 框架在结构上支持 iOS 和 Android 构建,但内核和 API 服务器仍会在设备上以进程内方式启动。
文件结构
crates/librefang-desktop/
build.rs # tauri_build::build()
Cargo.toml # Crate 依赖和元数据
tauri.conf.json # Tauri 应用配置
capabilities/
default.json # 主窗口的权限授予
gen/
schemas/ # 自动生成的 Tauri schema
icons/
icon.png # 源图标 (327 KB)
icon.ico # Windows 图标
32x32.png # 小图标
128x128.png # 标准图标
128x128@2x.png # HiDPI 图标
src/
main.rs # 二进制入口点(调用 lib::run())
lib.rs # Tauri 应用构建器、状态类型、事件监听器
commands.rs # IPC 命令处理器(get_port、get_status、get_agent_count)
server.rs # ServerHandle、内核启动、内嵌 axum 服务器
tray.rs # 系统托盘菜单和事件处理器
环境变量
| 变量 | 作用 |
|---|---|
RUST_LOG | 控制日志追踪的详细程度。未设置时默认为 librefang=info,tauri=info。 |
其他所有 LibreFang 环境变量(API 密钥、配置等)照常生效,因为桌面应用启动的内核与无头守护进程完全相同。
移动端(iOS & Android)
桌面应用是移动配对流程的服务端。如需了解远程连接此守护进程的 iOS / Android 轻客户端仪表板,请参阅移动端文档。