Browser-based chat interface. Supports user registration/login, invite-only mode, and persona isolation.
Requires Server mode.
{
"web": {
"enable": true,
"host": "",
"port": 8082,
"static_dir": "",
"upload_dir": "",
"persona_isolation": false,
"invite_only": true,
"preview_origins": []
},
"admin": {
"token": "your-secret-token"
}
}
| Field | Required | Default | Description |
|---|---|---|---|
enable | ✅ | false | Enable the Web channel |
host | ❌ | "" | Listen address (empty = all interfaces) |
port | ❌ | 0 | Listen port |
static_dir | ❌ | auto-detected | Path to the frontend static files directory |
upload_dir | ❌ | "" | Custom directory for uploaded files |
persona_isolation | ❌ | false | Isolate each web user’s persona from others |
invite_only | ❌ | true | Disable self-registration. Defaults to true (secure default) — the first account can still register (first-user bootstrap); after that only an admin can create accounts. Set explicitly to false to re-open public registration. |
preview_origins | ❌ | [] | External preview frontends (e.g. Vercel PR previews) allowed to request a short-lived backend grant. Empty disables preview authorization. Exact origins, or one single-label HTTPS pattern such as https://xbot-preview-*-<team>.vercel.app. XBOT_PREVIEW_ORIGINS (comma-separated) replaces it when set. Read at startup. |
WarningThe JSON key isenable(notenabled), unlike other channels.
In Server mode, the installer automatically downloads the Web UI to
~/.xbot/web/dist/.
For manual installation, download the web release archive and extract it to
~/.xbot/web/dist/.
After changing web/ sources, the deployment must be a whole-directory replacement:
bash scripts/deploy-web.sh # npm run build + atomic deploy + consistency check
bash scripts/deploy-web.sh --no-build # skip the build, deploy the current web/dist
WarningMixed artifacts are the easiest trap here: copying a freshly builtassets/into an old directory while keeping the oldindex.htmlmeans the served page still references the old bundle — new code never runs (the symptom is a feature that “seems to open” but does nothing).deploy-web.shcloses that path with a whole-directorymvplus a check that the bundle referenced byindex.htmlexists and contains a probe symbol; it exits 1 if the check fails.
After starting the server, open http://your-server:8082 in a browser.
Build a frontend for a path such as /xbot/session-shortcuts/:
cd web
XBOT_WEB_BASE_PATH=/xbot/session-shortcuts/ npm run build
Serve that build at the same path. Forward its api/, ws/, and plugins/
requests to the existing Web backend after removing the feature prefix. Keep
SSE unbuffered and preserve WebSocket upgrades. Rewrite backend cookie paths
to the preview namespace (and use Secure cookies over HTTPS) so preview login
does not overwrite a root deployment’s login. Unknown features/assets return
404; SPA routes such as login serve that feature’s index.html.
The default build base remains /. Subpath builds disable PWA caching and
worker updates. Login, page reloads and session browser tabs keep the feature
path; stored attachment references remain independent of the preview version.
This is a frontend preview, not a separate data environment: using the same
backend means edits and deletion affect real conversations. No backend restart
is needed. A local backend exposed through an SSH reverse tunnel requires the
local machine to remain online.
Vercel hosts only the static frontend. A preview build first shows a connection gate whose backend address defaults to http://localhost:8082; a successful probe is not authorization. The operator lists allowed preview origins in web.preview_origins (or XBOT_PREVIEW_ORIGINS, which replaces it) and restarts the backend once: exact origins, or one single-label deployment pattern such as https://xbot-preview-*-<team>.vercel.app so new deployments need no backend restart (each grant is still bound to the exact requesting origin). Log in on the backend, approve the origin shown on the consent page, then paste the short-lived token into the gate. The token is kept in memory only and carries full workspace abilities; it is not a read-only sandbox. See docs/vercel-preview.md. Never put credentials in Vercel, and never restart a backend automatically for a frontend deployment.
New session: you may pick a working directory; a missing directory is created automatically (including parent directories). If a regular file blocks the path (or permissions fail), the panel reports “failed to set working directory” instead of silently using a wrong one.
Rich-text (WYSIWYG) editor with Markdown shortcuts (**bold**, - lists, etc.) and links:
| Feature | Description |
|---|---|
| Links | Typing https://… followed by a space auto-links; pasted links and Markdown link syntax ([text](URL)) render live; links are highlighted in the theme accent color |
| Link editing | Select text to reveal a floating toolbar (bold/italic/strikethrough/inline code/link); Ctrl/Cmd+K adds or edits a link, one-click unlink |
| File upload | Any file type is accepted (no type whitelist). Click 📎, paste (screenshots upload automatically), or drag-and-drop files onto the composer to attach them |
| Upload limit | 10MB per file (size only — no type restrictions) |
| Terminal commands | A message starting with ! runs directly in the sandbox as a shell command and returns its output as a chat message — the AI is skipped (!ls -la, !git status, !docker ps). The composer shows a hint while the draft is a ! command; ![…] markdown images (pasted screenshots) are not commands |
Image previews in the composer fit within 240 × 120 px, preserve their aspect ratio, and shrink to fit narrow windows. Click an image to view it enlarged; press Esc, click the backdrop, or use the close button to dismiss it. Thumbnail sizing does not modify uploaded files or the image references sent with your message.
Deleting an uploaded image from the composer also removes its attachment chip, so it is not sent as a hidden attachment. Clicking the image chip’s × removes the matching image previews as well. Images with the same filename are tracked independently.
Native selection restoration is bounded to bodies with at most 16384 UTF-16 source/rendered characters and 256 text nodes. Larger bodies retain the captured comment quote without forced highlight restoration; streaming never pauses. Ordinary text sends and local /cancel, /tasks, and /rewind commands remain available during uploads; annotation sends wait for upload completion. Session-list previews may still show the raw annotation-envelope prefix, while message bubbles decode it.
Within these limits, selections retain their highlight and direction across appended Markdown updates when the selected text and preceding text stay unchanged. If Markdown formatting changes that text, the comment action still retains the captured quote.
Select text in an assistant response, including already visible excerpts while more text is appearing. After a mouse drag, a comment icon appears near the point where you release the mouse, kept within the visible viewport. Keyboard and native touch selection place it near the selection. Selecting the final paragraph, including its trailing line break, also supports comments; selecting actual tool text or another response does not. No right-click is needed; click the icon to open the comment editor. Streaming continues, and subsequent text updates keep the selected quote as a snapshot rather than removing the icon. On a phone, long-press to select text and adjust the native selection handles. The editor shows only the selected excerpt as a read-only snapshot, without turn/iteration labels or character counters. Press Enter to confirm the comment, or Shift+Enter for a newline; IME candidate confirmation never saves prematurely. Confirming adds a draft and does not send it. Clicking elsewhere, clearing the selection with the keyboard, scrolling, resizing or switching sessions dismisses the icon. Right-click menus remain dedicated to copying and links. Reasoning, tool output and user messages cannot be annotated.
Confirming a comment adds it to the comments chip inside the existing composer, alongside file attachments. The chip opens a preview with edit/delete actions. Adding or editing comments leaves your message and attachments unchanged and does not contact the agent. Send comments alone or together with a message and files; while the agent is busy, ordinary feedback is queued. Comments cannot be combined with slash/shell commands, goal mode or interject mode.
Confirmed comments are saved in this browser, separately by login username and channel/session, and restored on reload. Logging out clears them. A rejected send keeps the draft; a late acceptance does not erase newer text. Limits: 10 comments, 6000 Unicode code points per quote, 2000 per comment, 20000 in total. Oversized text is rejected, never truncated.
Sent feedback uses <response-annotations> containing a JSON array of {text, annotation, source}, followed by # My request: and your existing message. The agent receives the selected quote, your comment and the available source locations (turn/iteration/message ID and selection offsets). Offsets are zero-based, half-open UTF-16 positions in the rendered body’s concatenated DOM text, not raw Markdown positions. Old drafts without offsets remain usable; missing locations are not invented. Historical quotes are explicitly distinguished from your current instructions.
The chat bubble still shows only your message, quotes and comments, without XML or source labels; copying or editing the message retains the actual submitted XML prompt. Feedback follows the existing history/queue path. Source locations are snapshots, not permanent links or anchors after a rewind. This first version does not provide private notes, server-side/cross-device drafts, source highlights or restart-persistent send deduplication. Unchanged retries while the composer remains mounted reuse the request ID within the server’s existing deduplication window; refresh/restart recovery is not an exactly-once guarantee.
The session list is organised by project by default — a project is the session’s working directory (backend tenants.cwd). Sessions in the same directory share one group; its header shows the project name + session count (hover for the full path; sessions without a working directory land in “No work path”).
The view bar above the list switches the organisation (identical on the desktop sidebar and the mobile drawer):
| Category | Group key |
|---|---|
| Project (default) | working directory |
| Status | running / waiting for input / queued / unread / idle / error |
| Time | today / yesterday / earlier |
Collapse / expand: click a group header to collapse or expand that group; use “Collapse all / Expand all” on the right of the view bar to change every group at once. Collapse state is remembered per project (kept in localStorage; frontend-only, not synced across devices) and survives reloads and panel remounts.
Upgrading from an older version: the previous default was “by time” and the first load switches to “by project” once; a category you pick yourself is never changed afterwards.
Delete confirmation: choose Delete in a session’s context menu, then press Enter to confirm or Escape to cancel. Enter confirms even when Cancel has focus. IME confirmation and held-key repeats do not delete a session; while deletion is pending, the confirmation cannot submit another request.
Session shortcuts are desktop-only. Mobile mode hides their settings category and menu hints, and does not handle these seven bindings. Ordinary touch/menu actions and dialog confirmation remain available. This follows the effective UI mode in Settings > Appearance, including forced mobile mode. Switching modes does not clear your bindings; they remain available when you return to desktop.
Edit all seven bindings in the dedicated Settings > Keyboard Shortcuts category.
Interaction keeps code wrapping and the send-key mode. Type a
combination and press Enter or leave the field to save, or use the keyboard
icon to record it. The actions menu clears/disables a binding or restores its
default; Reset all shortcuts in the section header restores the full set. Duplicate session and
registered-command bindings are rejected. Changes apply immediately to both
actions and menu labels, persist on refresh, and sync with your account.
Custom Control and Command keys remain distinct; Mod accepts Ctrl/Cmd on
Mac/Linux and Ctrl only on Windows.
Unmodified text/navigation keys are not accepted (F1-F24 are supported).
Escape cancels recording; IME and held-key events are ignored. Generic events
named keydown are ignored; programmatic shortcut input must use KeyboardEvent.
System/browser
reserved combinations may never reach the page: enter a different binding
manually when recording cannot capture one. The table below lists defaults.
| Action | Mac | Linux | Windows |
|---|---|---|---|
| Create new session | Ctrl/Cmd+Alt+N | Ctrl/Cmd+Alt+N | Ctrl+Alt+N |
| Open existing session in browser tab | Ctrl+N | Ctrl+Shift+2 | Ctrl+Shift+2 |
| Star / unstar | Ctrl/Cmd+Alt+S | Ctrl/Cmd+Alt+S | Ctrl+Alt+S |
| Rename | F2 | F2 | F2 |
| Fork | Ctrl/Cmd+Alt+F | Ctrl/Cmd+Alt+F | Ctrl+Alt+F |
| Export (choose Native / OpenAI / Codex) | Ctrl/Cmd+Alt+E | Ctrl/Cmd+Alt+E | Ctrl+Alt+E |
| Open delete confirmation | Ctrl/Cmd+Alt+Backspace | Ctrl/Cmd+Alt+Backspace | Ctrl+Alt+Backspace |
Windows uses a separate shortcut configuration, initially populated with its own defaults rather than copying existing Mac/Linux bindings. Editing, disabling, or resetting shortcuts on Windows does not overwrite Mac/Linux preferences. Both configurations sync independently with your account. Product defaults map Command to Control and Option to Alt; Shift remains Shift. This does not translate saved custom modifiers or collapse Control+Command. Existing Windows overrides, including numeric bindings and disabled actions, are preserved. Physical key positions are used when modifiers produce symbols; AltGraph input is ignored. System/browser and keyboard-layout hotkeys can still conflict; all bindings are editable.
Create new session works globally, even with no current session or a hidden
session panel, and switches to the new conversation. Any previously customized
legacy openInTab binding (including a disabled binding) is preserved for
creation. The independent browser-tab action uses openInBrowserTab and opens
the existing conversation without creating or selecting a session. Its default
on Mac matches Control+N only, not Command+N. Linux and Windows default to
Control+Shift+2 and leave Control+N unassigned by default. Saved custom bindings,
including an explicit Linux Control+N, remain unchanged. Reset uses the current
platform default; browser-reserved combinations may need to be changed in Settings.
The other six shortcuts appear in the context menu (Alt is Option on macOS). They target the open session menu or focused session row first; otherwise they target the current session while the session panel is visible. Input fields, editors, focused AskUser choices/buttons, IME events, and held-key repeats are excluded from all seven actions. Creation pauses in any open dialog/menu; the other six ignore unrelated dialogs/menus and multi-select mode. Subagents and synthetic rows support the non-destructive browser-tab action from their row/menu; mutation shortcuts remain unavailable. Rename and fork require confirmation with Enter or their button; Escape cancels. Export opens the format submenu from a context menu, or a format dialog from a row/current session. The delete shortcut never deletes without confirmation.
| Method | Description |
|---|---|
| Username / password | Register and login; session cookies valid for 30 days |
| CLI token | WebSocket connection using the admin token |
| Feishu login | One-click login / link via Feishu account |
When invite_only is true:
- New users cannot self-register (receives 403)
- The admin can create accounts via Feishu admin commands or direct database operations
- Suitable for internal team use
A brand-new deployment has no accounts yet, so the first registration is
allowed — that account becomes the operator. The registration page says so
explicitly (“First-time setup · one-time only”) and states that registration
closes automatically afterwards, so a visitor never has to guess whether the
endpoint is open to everyone. Once the account exists, /register shows an
invite-only notice and the login page hides the register entry entirely.
The web UI ships two shells — desktop and mobile — and picks one automatically from the viewport width (≤767px → mobile shell). Narrow tablets, landscape phones and small desktop windows sit right on that boundary, so you can pin the shell manually under Settings → Appearance → UI mode:
| Mode | Behavior |
|---|---|
| Auto (default) | Follows the viewport breakpoint (≤767px = mobile shell) |
| Desktop | Always use the desktop shell (multi-panel / Dockview layout) |
| Mobile | Always use the mobile shell (drawer sessions + bottom nav) |
The preference is stored locally (localStorage) and takes effect immediately;
it also syncs to the server (web:ui:ui-mode) so other browsers/devices keep
the same choice. It only switches the shell — CSS breakpoints are
unchanged, so forcing the desktop shell on a narrow screen yields a compressed
desktop layout.
When persona_isolation is true:
- Each web user’s system persona is isolated from others
- User A’s agent behavior settings do not affect User B
- Suitable for multi-tenant scenarios
- Feishu Channel — team collaboration
- CLI Channel — terminal TUI
- Configuration — web channel settings
Settings → Storage switches the backend; saving takes effect immediately, no restart:
| Backend | Notes | Retention |
|---|---|---|
| Local (default, zero config) | Uploads go to <XBOT_HOME>/uploads/, served by the same-origin /api/files/download | Only the newest 500 uploads are kept after each upload (by mtime; no time limit — i.e. 500 ÷ uploads per day days) |
| Alibaba Cloud OSS / Tencent Cloud COS | S3-compatible: same credential fields, with the endpoint derived from the region (cn-hangzhou → oss-cn-hangzhou.aliyuncs.com; ap-guangzhou → cos.ap-guangzhou.myqcloud.com); an explicit endpoint overrides it (MinIO / R2 / self-hosted) | Cloud objects never expire automatically |
| Qiniu Kodo / S3 compatible | The bucket is the source of truth; a local copy is kept so the model can be handed a real path | Cloud objects never expire automatically (set a lifecycle rule in your cloud console if you want expiry) |
- Upload: paste/drag in the composer →
POST /api/files/upload(≤10MB, any file type) → returnsupload_key; the message stores only a relative reference(never base64; renders a placeholder if it is gone). - Fetch:
GET /api/files/download?key=…(download by default; add&inline=1to render inline). - Credentials are masked on read (
AKID****); echoing a masked value back never overwrites the real key. Switching to a cloud backend requires access/secret/bucket (Alibaba OSS / Tencent COS also need a region unless you provide an explicit endpoint), otherwise the save is rejected (a half-configured backend would break every upload). - For the model to actually see an image, also enable Settings → LLM → model row → Vision input.
Both the desktop session panel and mobile drawer offer Multi-select. Click or press Space/Enter to check a main session without changing the active conversation. Shift selects a range in display order. Select all includes only loaded, search-matching main sessions in expanded groups; SubAgents and synthetic placeholders are excluded. A scope hint appears when more sessions remain unloaded.
Batch actions include star, unstar, mark as read, export and delete. Export chooses one xbot / OpenAI / Codex format, with one file per session in a single ZIP download. Deletion requires confirmation showing the count and session names. Partial export/deletion failures are reported and remain selected for retry.
Clear selection keeps the mode active; Exit multi-select or Escape leaves it. Ctrl/Cmd+A selects all while focus is in the list, without intercepting editors or dialogs. Changing search, channel or category clears selection. Newly loaded sessions are never automatically checked.