6.9 KiB
AGENTS.md
These instructions cover the Rust runtime in runtime/. They add to the AGENTS.md in the repository
root, which applies here as well.
Rust Runtime (runtime/)
Entry point: runtime/src/main.rs
Key modules:
app_window.rs- Tauri window management, updater integrationapp_access.rs- Session cookie which lets the app window into the .NET serverdotnet.rs- Launches and manages the .NET sidecar processruntime_api.rs- Axum-based HTTPS API for .NET ↔ Rust communicationcertificate.rs- Generates self-signed TLS certificates for secure IPCsecret.rs- Secure secret storage using OS keyring (Keychain/Credential Manager)clipboard.rs- Cross-platform clipboard operationsfile_data.rs- File processing for RAG (extracts text from PDF, DOCX, XLSX, PPTX, etc.)encryption.rs- AES-256-CBC encryption for sensitive datapandoc.rs- Integration with Pandoc for document conversionlog.rs- Logging infrastructure usingflexi_logger
Every runtime API route requires the API token. require_api_token in runtime_api.rs checks it
for all routes at once, so handlers take no APIToken argument. Register a new route in
create_router, before that call: route_layer protects only the routes registered before it, and
a route added afterward would be open to every process on the machine. The test
every_route_requires_the_api_token reads the routes from create_router and fails for any route
which answers without the token.
Only the app window gets into the .NET server. Every start makes a new UI token in app_access.rs.
Rust passes it to .NET as AI_STUDIO_UI_TOKEN through ipc_environment in dotnet.rs, and hands it to
the window as an HttpOnly session cookie before the window opens the app (change_location_to in
app_window.rs); .NET refuses every request without it. Therefore:
- Never put the token into a URL or a log line. Only a development build hands it out elsewhere:
startup.envcarries it to the .NET server started from Rider, together with the other secrets of the IPC, and the boot link indev-boot-link.txtin the repository root lets another browser get the cookie. - Keep the cookie
Lax. A release showstauri://localhostbefore it opens the app, and WKWebView on macOS withholds aStrictcookie on that navigation. A development build starts from the dev server of the Tauri CLI on127.0.0.1, the same site as the app, which hides the problem; start it withcargo tauri dev --no-watch --no-dev-serverto see what a release sees. - The .NET server listens on
127.0.0.1only, and the cookie belongs to that host. Change the host inAPP_HOSTand inProgram.cstogether, since no test compares them.AppAccessRuntimeAgreementTestsonly checks that both sides use the same cookie name and environment variable.
Only the runtime lets links leave the window. AI Studio shows a single window, and a browser outside it cannot show the app, since it lacks the session cookie. Therefore:
should_open_in_system_browserinapp_window.rsalone decides which address goes to the system: a website, another local server, or amailto:ortel:link, but never an address of the app. The handlers for navigations and for new windows both ask it.- On Linux,
open_in_system_browserhands such an address to the desktop portal, as the Flatpak does anyway; the opener plugin only steps in when there is no portal. Started from an AppImage, xdg-open inherits the environment of the AppImage, and the browser stays closed without any error. - The handler for new windows always denies the window. Only a window which
setupbuilds itself can have that handler, which is whytauri.conf.jsonsets"create": false. - Keep the link script of the opener plugin switched off (
open_js_links_on_click(false)): it opens every link clicked while holding Shift or Ctrl in the system browser, links of the app included. The shell plugin has a link script as well, which cannot be switched off and opens every link with a target the same way. link_guard.jstakes every click which would open a link somewhere else away from both scripts and from the page. On a link of the app, the click does nothing; on any other link, it goes on to the webview, whose request for a new window or a navigation reaches the runtime. Therefore, give a link with a target no click handler of its own: no script of the page sees its click.- No script of the page may open or start anything, so
capabilities/default.jsongrants the opener and shell plugins nothing. The runtime calls both from Rust, which needs no permission, e.g., to start the .NET server indotnet.rs.
The right-click menu shows only in text fields and on selected text, never on a link: its other
entries, such as going back, reloading, or opening a link in a new window, make no sense in the app.
context_menu_guard.js hides it everywhere else, in every frame, but only in release builds, since
development builds need the menu to open the developer tools.
Runtime API handlers never block. All calls of the .NET app share one HTTP/2 connection, and the task driving it may wait on exactly the Tokio worker which a blocking handler occupies, so a single blocking handler can hold up the whole app. Therefore:
- File and disk access, the OS keyring, waiting for a
std::sync::Mutex, and CPU-bound work such as loading a tokenizer or scanning text run intokio::task::spawn_blocking. Followrun_qdrant_edge_requestinqdrant_edge_database.rs,prepare_imageandprepare_image_syncinimage.rs, orsanitize_batchinprompt_injection/api.rs. - When an API offers a callback, as the file dialogs do, await the callback instead of calling the
blocking variant; see
await_dialoginfile_actions.rs. - Short, bounded work, such as a single metadata lookup or writing a log line, may stay on the worker.
- When the blocking task fails, answer with an error, never with an empty value the app could take for a valid answer.
- Never keep a
std::sync::MutexGuardalive across an.await, not even with an explicitdropbefore it: the future of the handler is then no longerSend, and Axum refuses it. Scope the guard in a block instead. runtime_api::test_support::assert_runtime_stays_freetests a handler which waits for a lock. Run such a test with#[tokio::test(flavor = "multi_thread", worker_threads = 1)].
IPC Communication Flow
- Rust runtime starts and generates TLS certificate
- Rust starts internal HTTPS API on random port
- Rust launches .NET sidecar, passing: API port, certificate fingerprint, API token, secret key, UI token
- .NET reads environment variables and establishes secure HTTPS connection to Rust
- .NET requests an app port from Rust, starts Blazor Server on 127.0.0.1 at that port
- Rust hands the session cookie to the Tauri webview and points it to 127.0.0.1:app_port
- Bi-directional communication: .NET ↔ Rust via HTTPS API