Files
openclaw/docs/web/urls.md
Peter Steinberger 8b66fc103d feat(ui): durable session board face and dashboards index (#114262)
* feat(ui): durable session board face and dashboards index

Board face lived only in client-side boardSessionViews, capped at 50 entries,
so the preference never followed the user to another device, evicted as
sessions accumulated, and could not be seen as a set.

Persist it as SessionEntry.boardFace, which rides the existing entry_json blob
and so needs no SQLite schema change or version bump. Expose it on the session
list row and add it to the sessions.patch write-scope allowlist alongside label,
pinned, and archived: setting your own view preference is user-level chat
organization, not policy. Unknown patch fields still fail closed to
operator.admin.

Generic navigation now reads the stored face, so the sidebar and session list
open a thread on the face you left it on. boardSessionViews keeps only
activeTabId and reopenDockByTab, which are genuinely per-device.

Add /dashboards listing threads whose preferred face is dashboard. Filtering
runs server-side in filterSessionEntries before pagination, because the client
holds only a capped page and a client-side filter would silently omit
dashboards.

* test(protocol): assert the pre-rename face param is rejected

The gateway-protocol validator test still passed the pre-rename 'face' key,
which the closed schema rejects. Use boardFace, and pin the old name as a
negative case so it cannot silently return.

* chore(protocol): regenerate Swift bindings and docs map for boardFace

Adding boardFace to the sessions schema changes two committed generated
artifacts: the Swift gateway models (pnpm protocol:gen:swift) and the docs map
(pnpm docs:map:gen), which now lists the dashboards index section.
2026-07-27 00:35:34 -04:00

198 lines
12 KiB
Markdown

---
summary: "Control UI URL routes, stable session-link grammar, and connection handoff parameters"
read_when:
- You need to bookmark or share a Control UI session
- You are adding or changing a Control UI route
- You need a terminal, approval, onboarding, or remote Gateway URL
title: "Control UI URLs"
---
The Control UI uses readable paths for pages and session links. A configured
`gateway.controlUi.basePath` prefixes every path below. For example, `/chat/main`
becomes `/openclaw/chat/main` when the base path is `/openclaw`.
## Session and dashboard URLs
Chat and dashboard views are parallel route namespaces:
```text
/chat/main/deploy-monitor-6db92d48
/dashboard/main/deploy-monitor-6db92d48
/chat/main/telegram/12345
/chat/main/cron/nightly/run/8821
/chat/main
```
The path grammar is:
```text
/<namespace>/<agentId>
/<namespace>/<agentId>/<sessionRef>
/<namespace>/<agentId>/<restSegment>/<restSegment>...
```
`<namespace>` is either `/chat` or `/dashboard`. The first form opens that
agent's main session. The other forms encode one immutable session key in one of
two ways.
The short-id form applies when the session key's rest, everything after
`agent:<agentId>:`, ends in a UUID. `<sessionRef>` is an optional display-name
slug plus a short id, such as `deploy-monitor-6db92d48`. The short id is the
authoritative part: at least eight lowercase hexadecimal characters from the
start of the key's trailing UUID, with UUID dashes omitted. Longer prefixes up
to all 32 hexadecimal characters are accepted. The row's rotating `sessionId`
is not part of the URL identity.
Every other key uses the literal-key form. Each colon-delimited segment after
`agent:<agentId>:` becomes one URL-encoded path segment. For example,
`agent:main:telegram:12345` becomes `/chat/main/telegram/12345`, and
`agent:main:cron:nightly:run:8821` becomes
`/chat/main/cron/nightly/run/8821`.
Literal rest segments exactly equal to `.` or `..` use `~dot` and `~dotdot` so
browsers cannot collapse them as relative path segments. A literal segment that
starts with `~` doubles that leading character to keep the encoding reversible.
When an otherwise literal one-segment rest could be mistaken for a short id,
the builder inserts `~key` before it, for example
`agent:main:release-deadbeef` becomes
`/chat/main/~key/release-deadbeef`. The marker forces literal interpretation
and appears only when the unescaped form would be ambiguous.
The reserved single-segment literal rest names are `main`, `global`, `boot`,
and `sessions`. The configured `session.mainKey` joins that set at runtime.
Exactly one segment after the agent id is literal when it is reserved or does
not contain a valid short id; otherwise it is a short reference. Two or more
segments after the agent id are always literal.
Only the configured `session.mainKey` collapses to the agent-only main-session
path. With `session.mainKey: "workspace"`, `agent:research:workspace` becomes
`/chat/research`, while the distinct key `agent:research:main` remains the
literal path `/chat/research/main`.
### Stability contract
The following parts are stable URL contracts:
- The `/chat` and `/dashboard` namespace words.
- The key UUID short id in short-id URLs.
- The arity and short-versus-literal parsing rules above.
In short-id form, the agent segment and slug are explicitly decorative. They may
change without notice and are not used to identify or validate the session.
After resolution, the Control UI replaces the address bar with the current
agent id and current display-name slug without adding a browser-history entry.
In literal-key form, the agent segment is authoritative because it is part of
the reconstructed session key. The remaining literal segments are authoritative
too. A slug, when present, is always decorative; literal-key forms do not
synthesize one.
If one short id matches more than one session, the UI does not guess. It shows a
small disambiguation view with the matching display names, agents, and longer id
prefixes. Use a longer prefix to make the URL unique. Resolution examines at
most five pages of search results; if more remain, the view says that the search
was incomplete instead of guessing.
Canonical links do not use `?session=` or `?face=`. Released links such as
`/chat?session=<sessionKey>` are accepted only at the application boundary as a
migration aid and immediately rewritten, without adding browser history, to the
canonical path. The released `?face=dashboard` companion selects the
`/dashboard` namespace during that rewrite. Loaders and page code never read the
query-form identity, and new links must not emit it. The Sessions list keeps its
own `?session=` parameter because that parameter expands a row; it is not a
session deep link. The one-shot composer value `?draft=` remains supported on
chat and dashboard session paths.
## Route table
This table lists every Control UI application route. A dash means the route has
no route-specific URL parameters.
| Page | Canonical path | Aliases | Parameters or dynamic forms |
| ------------------- | --------------------------- | ------------------------- | ------------------------------------------------ |
| Chat | `/chat` | - | Key-backed session forms above; `?draft=<text>` |
| Dashboard | `/dashboard` | - | Key-backed session forms above; `?draft=<text>` |
| Dashboards | `/dashboards` | - | - |
| Ask OpenClaw | `/custodian` | - | `?intent=new-agent`, `?onboarding=1` |
| New session | `/new` | - | `?agent=<agentId>`, `?catalog=<catalogId>` |
| Activity | `/activity` | - | - |
| Apps | `/apps` | - | - |
| Agents | `/settings/agents` | `/agents` | `?agent=<agentId>` |
| Channels | `/settings/channels` | `/channels` | Shared settings parameters below |
| Connection | `/settings/connection` | - | Shared settings parameters below |
| General settings | `/settings/general` | `/config` | Shared settings parameters below |
| Profile | `/settings/profile` | `/profile` | Shared settings parameters below |
| Communications | `/settings/communications` | `/communications` | Shared settings parameters below |
| Appearance | `/settings/appearance` | `/appearance` | Shared settings parameters below |
| Notifications | `/settings/notifications` | - | Shared settings parameters below |
| Security | `/settings/security` | - | Shared settings parameters below |
| Advanced | `/settings/advanced` | - | Shared settings parameters below |
| Approvals | `/settings/approvals` | - | Shared settings parameters below |
| Automation settings | `/settings/automation` | `/automation` | Shared settings parameters below |
| MCP | `/settings/mcp` | `/mcp` | Shared settings parameters below |
| Infrastructure | `/settings/infrastructure` | `/infrastructure` | Shared settings parameters below |
| Labs | `/settings/labs` | - | Shared settings parameters below |
| About | `/settings/about` | - | Shared settings parameters below |
| AI and agents | `/settings/ai-agents` | `/ai-agents` | Shared settings parameters below |
| Model setup | `/settings/model-setup` | `/model-setup` | `?firstRun=1` |
| Model providers | `/settings/model-providers` | `/model-providers` | Shared settings parameters below |
| Import memory | `/memory-import` | `/settings/memory-import` | - |
| Workboard | `/workboard` | - | `/workboard/<boardId>` |
| Worktrees | `/worktrees` | `/settings/worktrees` | - |
| Sessions | `/sessions` | `/settings/sessions` | `?session=<sessionKey>`, `?status=archived\|all` |
| Usage | `/usage` | - | - |
| Debug | `/debug` | - | - |
| Logs | `/logs` | - | - |
| Skill Workshop | `/skills/workshop` | - | - |
| Skills | `/skills` | - | - |
| Plugins | `/settings/plugins` | - | `?tab=discover\|installed` |
| Automations | `/cron` | - | - |
| Tasks | `/tasks` | - | - |
| Devices | `/settings/devices` | `/nodes` | Shared settings parameters below |
| Plugin tab host | `/plugin` | - | `?plugin=<pluginId>&id=<tabId>` |
Settings routes that use schema-backed deep links accept `?section=<section>`,
`?advanced=1`, and `#<setting-id>`. These values select content within the page;
they do not change the route identity.
## Special documents and startup modes
These Gateway-served documents sit outside the application route table:
- `/?onboarding=1` opens the first-run onboarding presentation.
- `/?view=terminal` opens the full-screen terminal-only document used by the
mobile apps. Availability still requires `gateway.terminal.enabled` and
`operator.admin`.
- `/approve/<approvalId>` opens a standalone approval document. With a base
path, use `<basePath>/approve/<approvalId>`. The id identifies an approval but
never authorizes it; normal Gateway authentication still applies.
The approval namespace is reserved ahead of plugin HTTP routes for all HTTP
methods. When Control UI serving is disabled, it returns `404` instead of
falling through to a plugin route.
## Remote Gateway handoff
The Vite development UI can connect to a different Gateway:
```text
http://localhost:5173/?gatewayUrl=ws%3A%2F%2F<gateway-host>%3A18789
http://localhost:5173/?gatewayUrl=wss%3A%2F%2F<gateway-host>%3A18789#token=<gateway-token>
```
URL-encode a full `ws://` or `wss://` value. `gatewayUrl` is accepted only in a
top-level window, stored after load, and removed from the address bar. Prefer
`#token=` because fragments do not enter HTTP request logs or Referer headers.
The legacy `?token=` handoff remains a bootstrap-only credential fallback and
is stripped immediately. Passwords stay in memory only.
When `gatewayUrl` selects another Gateway, the UI does not fall back to local
configuration or environment credentials. Provide the remote Gateway's token
or password explicitly, and use `wss://` behind TLS.
## Related
- [Control UI](/web/control-ui)
- [Dashboard](/web/dashboard)
- [Session dashboards](/web/dashboards)