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}        |
|       +------------------------------------
+-------------------------------------------+

启动流程

  1. 初始化日志追踪 -- 配置 tracing_subscriber,使用 RUST_LOG 环境变量控制日志级别,默认值为 librefang=info,tauri=info
  2. 内核启动 -- LibreFangKernel::boot(None) 加载默认配置(来自 config.toml 或内置默认值),使用 Arc 包装。调用 set_self_handle() 以启用内核的自引用操作。
  3. 端口绑定 -- 在主线程上使用 std::net::TcpListener 绑定到 127.0.0.1:0,由操作系统分配一个随机可用端口。这确保在创建任何窗口之前就已获知端口号。
  4. 服务器线程 -- 生成一个名为 "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,并以优雅关闭模式运行服务。
  5. Tauri 应用 -- 组装 Tauri builder,包含插件、托管状态、IPC 命令、系统托盘,以及指向 http://127.0.0.1:{port} 的 WebView 窗口。
  6. 事件循环 -- 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"
URLhttp://127.0.0.1:{port}(外部地址)
Inner size1280 x 800
Minimum inner size800 x 600
Position居中

窗口使用 WebviewUrl::External(...) 而非打包的前端资源,因为 WebView 渲染的是 axum 提供的 UI。

自动更新

应用在启动 10 秒后检查更新。如有可用更新,会自动下载、安装并重启应用。用户也可通过系统托盘手动触发检查。

流程:

  1. 启动检查(10 秒延迟) → check_for_update() → 如有可用更新 → 通知用户 → download_and_install_update() → 应用重启
  2. 托盘 "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-devlibappindicator3-devlibrsvg2-devlibssl-devbuild-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-notification2为内核事件和更新进度提供原生操作系统通知
tauri-plugin-shell2从 WebView 访问 Shell/进程
tauri-plugin-dialog2Agent/技能导入的原生文件选择器
tauri-plugin-single-instance2防止多实例运行(仅桌面端)
tauri-plugin-autostart2操作系统登录时自动启动(仅桌面端)
tauri-plugin-updater2从 GitHub Releases 进行签名自动更新(仅桌面端)
tauri-plugin-global-shortcut2Ctrl+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 轻客户端仪表板,请参阅移动端文档。