Control UI (browser)
The Control UI is a small Vite + Lit single-page app served by the Gateway:- default:
http://<host>:18789/ - optional prefix: set
gateway.controlUi.basePath(e.g./openaeon)
Quick open (local)
If the Gateway is running on the same computer, open: If the page fails to load, start the Gateway first:openaeon gateway.
Auth is supplied during the WebSocket handshake via:
connect.params.auth.tokenconnect.params.auth.passwordThe dashboard settings panel lets you store a token; passwords are not persisted. The onboarding wizard generates a gateway token by default, so paste it here on first connect.
Device pairing (first connection)
When you connect to the Control UI from a new browser or device, the Gateway requires a one-time pairing approval — even if you’re on the same Tailnet withgateway.auth.allowTailscale: true. This is a security measure to prevent
unauthorized access.
What you’ll see: “disconnected (1008): pairing required”
To approve the device:
openaeon devices revoke --device <id> --role <role>. See
Devices CLI for token rotation and revocation.
Notes:
- Local connections (
127.0.0.1) are auto-approved. - Remote connections (LAN, Tailnet, etc.) require explicit approval.
- Each browser profile generates a unique device ID, so switching browsers or clearing browser data will require re-pairing.
What it can do (today)
- Chat with the model via Gateway WS (
chat.history,chat.send,chat.abort,chat.inject) - Stream tool calls + live tool output cards in Chat (agent events)
- Channels: WhatsApp/Telegram/Discord/Slack + plugin channels (Mattermost, etc.) status + QR login + per-channel config (
channels.status,web.login.*,config.patch) - Instances: presence list + refresh (
system-presence) - Sessions: list + per-session thinking/verbose overrides (
sessions.list,sessions.patch) - Cron jobs: list/add/edit/run/enable/disable + run history (
cron.*) - Skills: status, enable/disable, install, API key updates (
skills.*) - Nodes: list + caps (
node.list) - Exec approvals: edit gateway or node allowlists + ask policy for
exec host=gateway/node(exec.approvals.*) - Config: view/edit
~/.openaeon/openaeon.json(config.get,config.set) - Config: apply + restart with validation (
config.apply) and wake the last active session - Config writes include a base-hash guard to prevent clobbering concurrent edits
- Config schema + form rendering (
config.schema, including plugin + channel schemas); Raw JSON editor remains available - Debug: status/health/models snapshots + event log + manual RPC calls (
status,health,models.list) - Logs: live tail of gateway file logs with filter/export (
logs.tail) - Update: run a package/git update + restart (
update.run) with a restart report
- For isolated jobs, delivery defaults to announce summary. You can switch to none if you want internal-only runs.
- Channel/target fields appear when announce is selected.
- Webhook mode uses
delivery.mode = "webhook"withdelivery.toset to a valid HTTP(S) webhook URL. - For main-session jobs, webhook and none delivery modes are available.
- Advanced edit controls include delete-after-run, clear agent override, cron exact/stagger options, agent model/thinking overrides, and best-effort delivery toggles.
- Form validation is inline with field-level errors; invalid values disable the save button until fixed.
- Set
cron.webhookTokento send a dedicated bearer token, if omitted the webhook is sent without an auth header. - Deprecated fallback: stored legacy jobs with
notify: truecan still usecron.webhookuntil migrated.
Chat behavior
chat.sendis non-blocking: it acks immediately with{ runId, status: "started" }and the response streams viachatevents.- Re-sending with the same
idempotencyKeyreturns{ status: "in_flight" }while running, and{ status: "ok" }after completion. chat.historyresponses are size-bounded for UI safety. When transcript entries are too large, Gateway may truncate long text fields, omit heavy metadata blocks, and replace oversized messages with a placeholder ([chat.history omitted: message too large]).chat.injectappends an assistant note to the session transcript and broadcasts achatevent for UI-only updates (no agent run, no channel delivery).- Stop:
- Click Stop (calls
chat.abort) - Type
/stop(or standalone abort phrases likestop,stop action,stop run,stop openaeon,please stop) to abort out-of-band chat.abortsupports{ sessionKey }(norunId) to abort all active runs for that session
- Click Stop (calls
- Abort partial retention:
- When a run is aborted, partial assistant text can still be shown in the UI
- Gateway persists aborted partial assistant text into transcript history when buffered output exists
- Persisted entries include abort metadata so transcript consumers can tell abort partials from normal completion output
Chat manual (in-page)
The Chat page includes an in-page manual with two modes:- Quick Reference: command recipes, shortcut tips, and one-click draft insertion
- Guided Walkthrough: step-by-step flow from session start to persisted output verification
Visual semantics (fractal UI)
The fractal/“silicon life” visuals in Chat are state-driven, not decorative-only:- Delivery state (
running,persisted,persist_failed) influences warning/safe bands - Resonance values (
chaosScore,epiphanyFactor) influence depth and pulse intensity - Formula phase (
idle,active,error) controls recursive rail emphasis
Tailnet access (recommended)
Integrated Tailscale Serve (preferred)
Keep the Gateway on loopback and let Tailscale Serve proxy it with HTTPS:https://<magicdns>/(or your configuredgateway.controlUi.basePath)
tailscale-user-login) when gateway.auth.allowTailscale is true. OpenAEON
verifies the identity by resolving the x-forwarded-for address with
tailscale whois and matching it to the header, and only accepts these when the
request hits loopback with Tailscale’s x-forwarded-* headers. Set
gateway.auth.allowTailscale: false (or force gateway.auth.mode: "password")
if you want to require a token/password even for Serve traffic.
Tokenless Serve auth assumes the gateway host is trusted. If untrusted local
code may run on that host, require token/password auth.
Bind to tailnet + token
http://<tailscale-ip>:18789/(or your configuredgateway.controlUi.basePath)
connect.params.auth.token).
Insecure HTTP
If you open the dashboard over plain HTTP (http://<lan-ip> or http://<tailscale-ip>),
the browser runs in a non-secure context and blocks WebCrypto. By default,
OpenAEON blocks Control UI connections without device identity.
Recommended fix: use HTTPS (Tailscale Serve) or open the UI locally:
https://<magicdns>/(Serve)http://127.0.0.1:18789/(on the gateway host)
allowInsecureAuth does not bypass Control UI device identity or pairing checks.
Break-glass only:
dangerouslyDisableDeviceAuth disables Control UI device identity checks and is a
severe security downgrade. Revert quickly after emergency use.
See Tailscale for HTTPS setup guidance.
Building the UI
The Gateway serves static files fromdist/control-ui. Build them with:
ws://127.0.0.1:18789).
Debugging/testing: dev server + remote Gateway
The Control UI is static files; the WebSocket target is configurable and can be different from the HTTP origin. This is handy when you want the Vite dev server locally but the Gateway runs elsewhere.- Start the UI dev server:
pnpm ui:dev - Open a URL like:
gatewayUrlis stored in localStorage after load and removed from the URL.tokenis stored in localStorage;passwordis kept in memory only.- When
gatewayUrlis set, the UI does not fall back to config or environment credentials. Providetoken(orpassword) explicitly. Missing explicit credentials is an error. - Use
wss://when the Gateway is behind TLS (Tailscale Serve, HTTPS proxy, etc.). gatewayUrlis only accepted in a top-level window (not embedded) to prevent clickjacking.- Non-loopback Control UI deployments must set
gateway.controlUi.allowedOriginsexplicitly (full origins). This includes remote dev setups. gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=trueenables Host-header origin fallback mode, but it is a dangerous security mode.