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:
Authorization: Bearer <token>- the
xun_web_tokencookie (written by the login page, or when you visit/chat/with?token=;HttpOnly+SameSite=Strict, plusSecureover HTTPS) - the
?token=<token>query string — honoured only on the/chatpage: on a match the server sets the cookie and answers with a 303 redirect to the same URL minus thetokenparameter, 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
acceptedacknowledgement 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>/:
keyis a random token, the link itself is the credential (this path needs no token and only allowsGET/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.0exposes 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