feat(headless): Headless 服务器运行时与 liveagent-server(无需 Tauri) - #409
Open
thirsty5034 wants to merge 11 commits into
Open
feat(headless): Headless 服务器运行时与 liveagent-server(无需 Tauri)#409thirsty5034 wants to merge 11 commits into
thirsty5034 wants to merge 11 commits into
Conversation
Replace direct AppHandle/State usage in the command layer with a plain AppContext assembly plus a typed event emitter, so the same business commands can run under both the desktop Tauri runtime and a headless server. Desktop-only modules are feature-gated behind the default desktop feature. Tests updated to assert the compat abstraction instead of the raw tauri runtime paths.
A --no-default-features build strips the Tauri desktop runtime and runs
the same command surface over an axum HTTP/WebSocket server: GET /health,
GET /api/status, POST /api/invoke, GET /ws event broadcast, GET /* WebUI
SPA fallback and /proxy/{provider}/ BFF routes. Adds a same-origin
security model (CORS gate, optional Bearer token auth, rate limiting by
real peer IP) and a runtime-fallback mode for browsers without the Tauri
bridge.
Add a tauriBridge module exposing the same invoke interface to the existing GUI code so the WebUI runs unchanged on the headless server (runtime-fallback over fetch/WebSocket). Adds HeadlessFolderPicker and headless-aware file upload. All GUI imports now go through the bridge instead of @tauri-apps/api directly.
scripts/manifest/commands.json is the committed source of truth for the 234 Tauri commands; build_type_map.py derives the Rust type map, gen_adapters.py regenerates commands/adapters.rs and verify_headless.py asserts headless.rs dispatch arms match the manifest both ways. The gen-verify CI job fails on any drift.
Add headless-rust (cargo check/test/build --no-default-features) and gen-verify (generator drift check) jobs. Document the headless security model and the command-registry workflow in the README.
5 tasks
Contributor
Author
|
按 #379 的评审意见完成拆分,提交历史也已整理为少量逻辑提交:
建议合并顺序:先审本 PR,再审 #410。两个 PR 的 CI workflow 需要 Approve 后才会运行。 |
# Conflicts: # crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs # crates/agent-gui/src-tauri/src/lib.rs # crates/agent-gui/src/lib/chat/history/chatHistory.ts
…owser mode agent-gui (headless WebUI) previously re-exported @tauri-apps/api modules (tauriCore/tauriEvent/tauriOpener) which are native-Tauri-only and throw in the browser, breaking all agent-ui fs/memory/proxy/settings paths (workspaces all red, cannot add/activate workspace). Re-export from lib/tauriBridge instead so invoke/listen/openUrl/revealItemInDir fall back to the WebSocket/fetch bridge in headless mode. Verified: tsc + vite build pass; new bundle's fsBackend invoke now imports from tauriBridge chunk (fetch /api/invoke) instead of @tauri-apps/api/core.
The headless WebUI (agent-gui) handled system_pick_folder by simply returning the initial_workdir, so clicking '打开本地文件夹' closed the clone modal with no visible result (no directory picker). Intercept system_pick_folder in lib/tauriBridge for non-Tauri runtimes and show a browser prompt asking for the absolute server path, mirroring the agent-gateway web shim. The headless server still validates the path.
…save) Upstream added the model-failover settings command; the business fn was already merged into commands/config/settings/commands.rs but headless.rs had no dispatch arm and the manifest was stale, so saving model failover settings from the browser UI failed with 'unknown command'. - add dispatch arm in headless.rs (mirrors settings_save_memory) - register settings_save_model_failover in scripts/manifest/commands.json - regenerate commands/adapters.rs (235 adapters) - verify_headless.py: manifest 235 == dispatch 235
…load
The upstream merge dropped the wired-up versions of two headless WebUI
features; the components/endpoints survived but the glue was lost:
1. system_pick_folder fell back to a bare absolute-path text prompt
(HeadlessFolderPicker.tsx existed but was never wired). Route the
headless intercept through openFolderPicker() instead, so the user can
browse the server's filesystem (breadcrumbs + fs_list_dirs listing +
fs_roots quick locations) rather than typing a path by hand. Dynamic
import breaks the static cycle HeadlessFolderPicker -> fsBackend ->
shims/tauriCore -> tauriBridge.
2. 'pick files' in headless mode called system_pick_readable_files, which
errors ('file picker is unavailable in headless mode') because there is
no native dialog. Restore the browser <input type=file> fallback
(pickBrowserFiles) and route headless uploads (pick + drag&drop + paste)
through the multipart endpoint POST /api/files/import via
importReadableFilesViaMultipart (no base64 +33% expansion, no 413 on
larger files). Tauri desktop paths unchanged.
Verified: tsc, biome (changed files), vite build (HeadlessFolderPicker
emitted as its own lazy chunk), verify_headless.py 235==235, 1509 frontend
tests pass.
The proxy baseUrl was hardcoded to a loopback (http://127.0.0.1:17890). When the WebUI is reached through a public domain / reverse proxy (https://liveagent.code.pp00.top), the frontend built cross-origin proxy URLs pointing at 127.0.0.1, which the browser blocked via CORS (no Access-Control-Allow-Origin, plus HTTPS->HTTP mixed content), so model listing showed 'Failed to fetch' on the domain entry while the loopback entry worked. Now /api/invoke reads the browser Origin header and returns it as the proxy baseUrl, so proxy requests stay same-origin on the accessing entrypoint. Falls back to the startup loopback baseUrl for desktop/curl callers without an Origin.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
关联 issue
Closes #380
概述
为 LiveAgent 增加 Headless 运行时:桌面版通过
#[tauri::command]暴露的业务命令面,现在同样以独立的 axum HTTP/WebSocket 服务形式运行(--no-default-features,不依赖 Tauri),并通过页内桥接复用现有 GUI 前端。同时提供基于命令清单的生成器流水线,保证 234 个命令适配层可复现、可校验,CI 中带防漂移检查。5 个逻辑 commit,144 个文件(+13,886 / −1,534)。
改动范围
src-tauri(服务端运行时 / 命令分发)、crates/agent-gui+crates/agent-gateway(桥接 + 同源安全模型)、scripts/(清单生成器 + 校验器)、.github/workflows/ci.yml(headless-rust + gen-verify 任务)src-tauri/src/headless.rs— axum 路由:GET /health、GET /api/status、POST /api/invoke、POST /api/files/import、GET /ws、GET /*SPA 回退src-tauri/src/tauri_bridge.rs— 同接口桥接,让 WebUI 可在 headless 下运行scripts/manifest/commands.json— 提交的权威命令清单(234 个命令)scripts/gen_headless.sh/verify_headless.py— 生成器 + 双向防漂移校验src-tauri/src/commands/adapters.rs— 桌面端薄适配层主要亮点
AppHandle/State/api/invokeToken 鉴权(LIVEAGENT_API_TOKEN)、/ws来源校验、限流按真实 TCP 对端地址计算commands.json;适配层与分发分支由生成器产出并在 CI 校验(git diff --exit-code)liveagent-server二进制可独立安装运行,无需桌面窗口截图 / 预览
Headless WebUI(复用现有 GUI,桥接至服务器)运行效果:
验证情况
cargo build --no-default-features通过(由新增的headless-rustCI 任务守护)scripts/gen_headless.sh重新生成无漂移;verify_headless.py双向校验清单 ↔ 分发分支一致(234 = 234)liveagent-server启动后 WebUI 经tauriBridge正常工作(health / api / ws / 文件导入均已在部署环境验证)提交前检查