* feat(desktop): add native host scaffold * feat(webui): track turns and usage in gateway * feat(webui): polish desktop chat experience * feat(apps): add ArcGIS and Joplin logos * feat(desktop): polish shell and shared surfaces * fix(webui): avoid preview chips for glob references * test: align CI expectations for token fallback * feat(webui): preview prompt rail entries * feat(webui): add prompt navigator drawer * style(webui): refine prompt navigator placement * style(webui): align prompt navigator with header actions * style(webui): simplify prompt navigator header * refactor(webui): clean thread resource refresh * feat(desktop): add native reply notifications * fix(webui): preserve desktop restart and replay state * fix(desktop): harden gateway proxy startup * fix(web): fall back when readability is unavailable * fix(desktop): hide window instead of closing on macos * fix(webui): unify desktop header actions * fix(webui): simplify prompt history rows * fix(desktop): log notification delivery failures * chore(desktop): clean source package artifacts * fix(cron): support one-time relative reminders * fix(webui): reveal scroll button in place * Revert "fix(cron): support one-time relative reminders" This reverts commit 4c4661da120a3c7283e0768412bae48604e7390b. * refactor(webui): extract token usage heatmap * docs(desktop): clarify contributor guides --------- Co-authored-by: chengyongru <2755839590@qq.com>
3.4 KiB
Native Host Contract
This is a contributor reference for the boundary between the shared WebUI and the native desktop host. Users should not need this contract to run the app, but it explains why the desktop app can use native capabilities without turning the WebUI into Electron-specific code.
desktop is a native host shell around the shared WebUI build. The renderer
must not import Electron directly. It receives a minimal bridge at
window.nanobotHost.
Runtime API
type HostRuntimeInfo = {
surface: "native";
app_version: string;
engine_status: "starting" | "ready" | "restarting" | "stopped" | "crashed";
data_dir: string;
logs_dir: string;
config_path: string;
workspace_path: string;
python: string;
engine_transport?: "unix_socket";
};
type HostSocketEvent =
| { id: string; type: "open" }
| { id: string; type: "message"; data: string }
| { id: string; type: "error"; message: string }
| { id: string; type: "close"; code?: number; reason?: string };
type NanobotHost = {
getRuntimeInfo(): Promise<HostRuntimeInfo>;
restartEngine(): Promise<void>;
pickFolder(): Promise<string | null>;
openLogs(): Promise<void>;
exportDiagnostics(): Promise<string>;
checkForUpdates(): Promise<{ supported: boolean; message?: string }>;
openSocket(url: string): Promise<string>;
sendSocket(id: string, data: string): Promise<void>;
closeSocket(id: string): Promise<void>;
onSocketEvent(listener: (event: HostSocketEvent) => void): () => void;
onRuntimeStatus(listener: (status: HostRuntimeInfo["engine_status"]) => void): () => void;
};
First Run
The desktop host starts the private engine immediately. If the native data
directory has no config.json, nanobot desktop-gateway creates one with
defaults before serving the shared WebUI. Provider, model, credential, and login
setup stay in WebUI settings instead of Electron-owned HTML.
Socket Bridge
The engine listens on a per-user Unix socket under the app data directory.
/webui/bootstrap returns runtime_surface: "native" and a WebSocket URL in
the nanobot-host://engine/... scheme. WebUI never opens that URL directly in
the browser runtime; it hands the URL to window.nanobotHost.openSocket.
The native host then performs the WebSocket handshake against the Unix socket and forwards events over Electron IPC.
Host Security Boundary
The host bridge is intentionally narrower than a general Electron preload:
- IPC calls are accepted only from renderer frames loaded from
nanobot-app://app/.... openSocketaccepts onlynanobot-host://engine/...URLs.- External navigation is denied in the app window; safe web links are opened by the operating system.
- Native WebUI responses carry a restrictive Content Security Policy and
X-Content-Type-Options: nosniff. - The renderer runs with
nodeIntegration: false,contextIsolation: true,sandbox: true, andwebSecurity: true.
Security-sensitive tool behavior still belongs in nanobot core. The host protects the native app boundary; the engine protects file, network, and tool permissions.
Data Directory
The host stores config, workspace, sessions, logs, and transient socket files under Electron's platform app data directory. In development on macOS this is usually:
~/Library/Application Support/@nanobot/desktop/
Packaged builds use the packaged app name.
The app bundle is replaceable. User data is not stored in the bundle.