Summary
Pairing the Omadia UI desktop app to an Omadia host is currently a manual, brittle step: the user has to hand-type the full canvas transport URL (wss://<host>/omadia-ui/canvas), including scheme and path suffix. That URL is not the URL a human knows — in a split deployment (Next.js operator front + middleware behind, different hosts) the transport URL differs from the browser/operator URL, and there is no in-product way to discover it. This request proposes friction-free pairing for the two deployment shapes an open-source, self-hostable product must serve: host on the same LAN and host on the public internet.
Motivation
Discovered during a real pairing attempt against a test instance (odoo-bot-harness.fly.dev): the operator/web-ui front gates everything behind a Next.js login and does not route the canvas WebSocket; the canvas WS lives on the separate middleware service. Entering the operator URL loops at the connect step, and the working transport URL is only recoverable from a colleague's saved settings.json or via Fly CLI. For self-hosters this is a non-starter.
Design principle: the server owns the mapping "human-facing URL → transport URL", not the user. The app should need either nothing (LAN) or only the one URL the user already knows (remote).
Scenario A — Omadia host on the same LAN (zero-config, Bonjour/mDNS)
The common case for self-hosting (cf. Home Assistant, Plex, Syncthing, Jellyfin).
- Host side (middleware): advertise an mDNS service
_omadia._tcp with TXT records carrying:
path (canvas WS path, e.g. /omadia-ui/canvas)
proto (canvas protocol version, e.g. 1.0)
auth (auth mode: none | password | oidc)
name (human instance label)
- Client side (Omadia UI): browse
_omadia._tcp in the Electron main process (e.g. bonjour-service), present a "Discovered hosts" picker — click to connect, zero typing.
Scenario B — Omadia host on the public internet (URL discovery)
- User enters the one URL they already know (the operator/browser URL,
https://…) — no scheme juggling, no /omadia-ui/canvas suffix.
- Client GETs an un-gated discovery endpoint on that origin (e.g.
/.well-known/omadia-ui or a public /omadia-ui/info) which returns:
wsUrl — absolute canvas WS URL incl. host (may be the same origin or a separate middleware host)
auth — providers + login-start URL
protocolVersion
- Client authenticates via the discovered provider → session cookie → connects to the discovered
wsUrl.
Manual fallback
Full wss://…/omadia-ui/canvas paste — last resort for debugging / exotic setups only.
Unifying abstraction
All three paths produce the same descriptor and the connect code stays source-agnostic:
{ wsUrl, protocolVersion, auth }
mDNS discovery and HTTP discovery return the same shape; the picker, the URL-discovery flow, and manual entry all feed it.
Concrete work
Server-side (this repo, byte5ai/omadia):
- Expose the discovery endpoint before the login gate (public) and have it return absolute URLs (host included), not relative paths.
- Either have web-ui proxy the canvas WS, or have discovery return the middleware's public host — one of the two must exist.
- Align the auth path: the desktop app calls
/api/v1/auth/*, the deployment exposes /bot-api/v1/auth/*.
- Add the mDNS advertiser to the middleware (
_omadia._tcp + TXT).
Client-side (byte5ai/omadia-ui):
- Accept
https:// input and derive wss:// automatically.
- mDNS browser + "Discovered hosts" picker UI.
- Consume the discovery descriptor; keep manual paste as fallback.
Existing half
/omadia-ui/info already returns { transport: 'websocket', websocket: '/omadia-ui/canvas', dispatchService, protocolVersions, … } — but it is currently gated (302 → /login) and returns a relative path. Making it public + absolute covers most of Scenario B.
Filed from the Omadia UI side after a pairing session; client work tracked in byte5ai/omadia-ui.
Summary
Pairing the Omadia UI desktop app to an Omadia host is currently a manual, brittle step: the user has to hand-type the full canvas transport URL (
wss://<host>/omadia-ui/canvas), including scheme and path suffix. That URL is not the URL a human knows — in a split deployment (Next.js operator front + middleware behind, different hosts) the transport URL differs from the browser/operator URL, and there is no in-product way to discover it. This request proposes friction-free pairing for the two deployment shapes an open-source, self-hostable product must serve: host on the same LAN and host on the public internet.Motivation
Discovered during a real pairing attempt against a test instance (
odoo-bot-harness.fly.dev): the operator/web-ui front gates everything behind a Next.js login and does not route the canvas WebSocket; the canvas WS lives on the separate middleware service. Entering the operator URL loops at the connect step, and the working transport URL is only recoverable from a colleague's savedsettings.jsonor via Fly CLI. For self-hosters this is a non-starter.Design principle: the server owns the mapping "human-facing URL → transport URL", not the user. The app should need either nothing (LAN) or only the one URL the user already knows (remote).
Scenario A — Omadia host on the same LAN (zero-config, Bonjour/mDNS)
The common case for self-hosting (cf. Home Assistant, Plex, Syncthing, Jellyfin).
_omadia._tcpwith TXT records carrying:path(canvas WS path, e.g./omadia-ui/canvas)proto(canvas protocol version, e.g.1.0)auth(auth mode:none|password|oidc)name(human instance label)_omadia._tcpin the Electron main process (e.g.bonjour-service), present a "Discovered hosts" picker — click to connect, zero typing.Scenario B — Omadia host on the public internet (URL discovery)
https://…) — no scheme juggling, no/omadia-ui/canvassuffix./.well-known/omadia-uior a public/omadia-ui/info) which returns:wsUrl— absolute canvas WS URL incl. host (may be the same origin or a separate middleware host)auth— providers + login-start URLprotocolVersionwsUrl.Manual fallback
Full
wss://…/omadia-ui/canvaspaste — last resort for debugging / exotic setups only.Unifying abstraction
All three paths produce the same descriptor and the connect code stays source-agnostic:
mDNS discovery and HTTP discovery return the same shape; the picker, the URL-discovery flow, and manual entry all feed it.
Concrete work
Server-side (this repo,
byte5ai/omadia):/api/v1/auth/*, the deployment exposes/bot-api/v1/auth/*._omadia._tcp+ TXT).Client-side (
byte5ai/omadia-ui):https://input and derivewss://automatically.Existing half
/omadia-ui/infoalready returns{ transport: 'websocket', websocket: '/omadia-ui/canvas', dispatchService, protocolVersions, … }— but it is currently gated (302 → /login) and returns a relative path. Making it public + absolute covers most of Scenario B.Filed from the Omadia UI side after a pairing session; client work tracked in
byte5ai/omadia-ui.