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.

File panel

Sessions started by xuns enable expose_files, which provides directory browsing, PDF/full-screen preview, syntax highlighting in the text preview, inline folder creation and rename/move, and directory upload (the result notification lists every file and then disappears automatically; a file with an existing name is overwritten silently). Sizes in the file details are shown with a matching magnitude (B / KiB / MiB …, at most one decimal). All file operations are restricted to the agent's working directory; anything outside it returns 400, and symbolic links are skipped.

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