Skip to content

xuns · Web service

Use the same agent in a browser: chat interface, event stream, collapsible tool-activity blocks, file panel, and multi-session switching with renaming.

xuns .                                   # all sessions share the current directory
xuns                                     # one temporary working directory per session
xuns . --host 0.0.0.0 --port 18960 --base-path /xun
xuns --no-initial-agent .                      # no agent at all, only for browsing /docs/

Parameters

Parameter Form Default Description
workdir positional, optional empty working directory shared by all sessions; when omitted or empty, each session gets its own temporary directory (cleaned up on exit)
--host string localhost listen address; with 0.0.0.0 the startup message additionally prints the aka localhost form
--port integer 18960 port; 0 means the system assigns a random port, and the actual port is printed
--token string empty access token; an empty value is auto-generated with secrets.token_urlsafe(24)
--base-path string empty URL prefix for sub-path deployments; must not contain empty segments, . or ..
--manage-sessions / --no-manage-sessions boolean flag on whether sessions may be created/deleted/renamed in the interface
--initial-agent / --no-initial-agent boolean flag on whether an initial agent is created at startup. When off, the process only serves the static UI and the /docs/ site, so the manual can be browsed without configuring an LLM; as long as --manage-sessions stays on, the UI can still create sessions, and creating one builds an agent, which needs a usable LLM configuration

The corresponding Python API:

from xun import web_session

web_session(
    workdir=".", host="localhost", port=18960, token="",
    base_path="", manage_sessions=True, initial_agent=True,
)

Once started it prints a direct URL for each session (the token is already included in the query string):

Agents are available at the following URLs:
http://localhost:18960/chat/?session=%2F&token=...

Tokens and login

--token is the access credential; the server compares it with hmac.compare_digest and accepts three ways of supplying it:

  1. Authorization: Bearer <token>
  2. the xun_web_token cookie (written by the login page, or when you visit /chat/ with ?token=; HttpOnly + SameSite=Strict, plus Secure over HTTPS)
  3. the ?token=<token> query string — honoured only on the /chat page: on a match the server sets the cookie and answers with a 303 redirect to the same URL minus the token parameter, so API and WebSocket requests cannot authenticate through the query string

An unauthenticated /chat page request is answered with a 303 redirect to the login page at --base-path/login; every other unauthenticated request (static assets and API) returns 401 {"detail":"Not authenticated"}; unauthenticated WebSockets are closed with 1008. The documentation site /docs/ needs no login, so the manual stays easy to browse.

Route overview

All paths below carry the --base-path prefix (empty when it is not set).

Path Description
/ redirects to /chat/
/chat/ the front-end interface (static assets, built from web/)
/docs/ bilingual Chinese/English documentation site (packaged in src/xun/assets/docs), public access
/login login page; ?next= only accepts in-site addresses pointing to /chat, anything else falls back to /chat/
/api/sessions session list, creation, removal and renaming (POST /api/sessions/create, /remove, /rename)
/session/, /session/<uuid>/ the backend display instance of each session (WebSocket /ws, /api/events, /api/prompts, /api/files/...)
/session/<path>/srv/<key>/ temporary static directory hosting, see below

Session state is reported by the list endpoint as idle / running / waiting (waiting = a prompt is awaiting an answer). The last remaining session cannot be deleted; with --manage-sessions turned off, the create/remove/rename endpoints return 405. Session names can be edited in the sidebar (at most 80 characters after trimming, an empty name answers 422); renaming only changes what the interface shows, never the mount path.

How the event stream is presented

User messages stand on their own instead of being folded into a turn; tool calls and results collapse into activity blocks, and only a single-agent block skips the inner nesting — multi-agent sessions still unfold one level at a time. The newest activity block carries an activity preview under its header: at most 3 lines, each holding the first 160 characters of an event, with newer previews pushing older ones out; a running indicator shows whenever any agent in that block is working.

Message channel and reconnection

Every message / command the front end sends carries a client-generated client_id (a UUID):

  • the server replies with an accepted acknowledgement only after taking the message in, and the front end clears the input box and its attachments once that acknowledgement arrives; until then the text stays on screen, so a message that was sent but never applied is never invisible.
  • if the socket drops, the unacknowledged submission is resent automatically after reconnecting, and the server deduplicates by client_id (the most recent 2000 are remembered), so one message cannot run twice.
  • messages without a client_id (for instance from a client you wrote yourself) are processed as before, just without acknowledgement or deduplication.

See Display & web service for the exact fields and endpoints.

Every session registers the complete set of built-in commands, including /save, /load, /render and /handoff — the commands that read and write the workspace — and they are equally available in the / input box.

File panel

Sessions started by xuns enable expose_files, which provides:

  • directory browsing and preview: images, PDF (including full screen), text with syntax highlighting; Markdown / HTML is always previewed as its rendered output, a renderable file opens straight into the preview, and the "Render" button is listed before "Source".
  • text editing: the preview can be edited and saved in place, and new text files can be created; saving carries the modified_at value from when the file was opened for conflict detection, so a file already changed elsewhere is refused rather than silently overwritten.
  • inline folder creation (several levels at once), rename/move, delete, and directory upload (the result notification lists every file and then disappears automatically; a file with an existing name is overwritten outright).
  • sizes in the file details are shown with a matching magnitude (B / KiB / MiB …, at most one decimal), together with is_text.

All file operations are restricted to the agent's working directory; anything outside it returns 400, and symbolic links are skipped. The settings menu in the sidebar shows the version and commit the front end was built from, so you can confirm which build of the UI you are looking at.

Serving static directories /srv/

If the working directory ends up containing a generated static site (for example this project's site/), you can mount it in the interface as GET /session/<path>/srv/<key>/:

  • key is a random token, the link itself is the credential (this path needs no token and only allows GET / HEAD)
  • it expires after 1 hour by default (SERVE_TTL_SECONDS = 3600), and at most 16 may be served at a time (exceeding the limit returns 429)
  • passing an empty path serves the working directory root itself

Deployment notes

  • The interface and the backend communicate over WebSocket, so a reverse proxy must allow upgrade requests and preserve X-Forwarded-Host / X-Forwarded-Proto.
  • Sub-path deployment only needs --base-path; all assets and language-switcher links are relative paths, so any prefix works.
  • --host 0.0.0.0 exposes the service on every network interface — combine it with a firewall or reverse-proxy authentication.

Front-end development: npm run dev uses concurrently to bring up the backend (xuns, needs uv) and Vite (Vue DevTools) in parallel; use npm run dev:ui if you only want the UI.

cd web && npm install && npm run dev     # UI http://127.0.0.1:5173