nanobot/desktop/docs/host-contract.md
Xubin Ren ab9f49970d
feat(desktop): polish desktop shell and shared WebUI surfaces (#4195)
* 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>
2026-06-06 19:49:33 +08:00

95 lines
3.4 KiB
Markdown

# 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
```ts
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/...`.
- `openSocket` accepts only `nanobot-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`, and `webSecurity: 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:
```text
~/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.