diff --git a/ui/src/app/app-host.ts b/ui/src/app/app-host.ts
index 48b25737bf1f..a47419a44667 100644
--- a/ui/src/app/app-host.ts
+++ b/ui/src/app/app-host.ts
@@ -15,7 +15,7 @@ import "../components/terminal/terminal-panel.ts";
import "../components/tooltip.ts";
import "../components/update-banner.ts";
import type { SidebarNavRoute } from "../app-navigation.ts";
-import { APP_ROUTE_IDS, isRouteId, pathForRoute, type RouteId } from "../app-routes.ts";
+import { APP_ROUTE_IDS, isRouteId, type RouteId } from "../app-routes.ts";
import {
COMMAND_PALETTE_TARGET_EVENT,
type CommandPalette,
@@ -27,8 +27,6 @@ import { copyToClipboard } from "../lib/clipboard.ts";
import { isGatewayMethodAdvertised } from "../lib/gateway-methods.ts";
import { isWorkboardEnabledInConfigSnapshot } from "../lib/plugin-activation.ts";
import { searchForSession } from "../lib/sessions/index.ts";
-import { resolveAgentIdFromSessionKey } from "../lib/sessions/session-key.ts";
-import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "../lib/string-coerce.ts";
import { renderDevicePairSetup } from "../pages/nodes/view-pairing.ts";
import { pluginTabKey, pluginTabRefFromSearch } from "../pages/plugin/route.ts";
import { bootstrapApplication, type ApplicationRuntime } from "./bootstrap.ts";
@@ -70,18 +68,6 @@ function equalShellRouteState(previous: ShellRouteState, next: ShellRouteState):
);
}
-function resolveAgentLabel(sessionKey: string, agentsList: AgentsListResult | null): string {
- const agentId = resolveAgentIdFromSessionKey(sessionKey);
- const agent = agentsList?.agents.find(
- (entry) => normalizeLowercaseStringOrEmpty(entry.id) === agentId,
- );
- return (
- normalizeOptionalString(agent?.identity?.name) ??
- normalizeOptionalString(agent?.name) ??
- agentId
- );
-}
-
function resolveOnboardingMode(): boolean {
const raw = new URLSearchParams(globalThis.location?.search ?? "").get("onboarding");
return raw !== null && /^(?:1|true|yes|on)$/iu.test(raw.trim());
@@ -123,10 +109,14 @@ function isTerminalAvailable(
}
return (
hasOperatorAdminAccess(snapshot.hello?.auth ?? null) &&
- isGatewayMethodAdvertised(snapshot, "terminal.open") === true
+ isGatewayMethodAdvertised(snapshot, "terminal.open")
);
}
+function isMobileNavLayout(): boolean {
+ return globalThis.matchMedia?.("(max-width: 1100px)").matches ?? false;
+}
+
class OpenClawApp extends LitElement {
@state() private gatewayConnected = false;
@state() private gatewayReconnecting = false;
@@ -376,7 +366,6 @@ class OpenClawShell extends LitElement {
@state() private terminalAvailable = false;
@state() private terminalClient: GatewayBrowserClient | null = null;
@state() private activeSessionKey = "";
- @state() private agentLabel = "";
@state() private routeState: ShellRouteState = {};
@state() private overlaySnapshot: ApplicationOverlaySnapshot = {
updateAvailable: null,
@@ -396,7 +385,6 @@ class OpenClawShell extends LitElement {
private navDrawerTrigger: HTMLElement | null = null;
private agentsListClient: GatewayBrowserClient | null = null;
private sessionKeyClient: GatewayBrowserClient | null = null;
- private stopAgentsSubscription: (() => void) | undefined;
private stopConfigSubscription: (() => void) | undefined;
private stopGatewaySubscription: (() => void) | undefined;
private stopNavigationSubscription: (() => void) | undefined;
@@ -413,6 +401,7 @@ class OpenClawShell extends LitElement {
super.connectedCallback();
this.startSubscriptions();
this.addEventListener(COMMAND_PALETTE_TARGET_EVENT, this.handleCommandPaletteTarget);
+ document.addEventListener("keydown", this.handleDocumentKeydown);
}
override updated() {
@@ -425,7 +414,6 @@ class OpenClawShell extends LitElement {
if (
!runtime ||
!context ||
- this.stopAgentsSubscription ||
this.stopConfigSubscription ||
this.stopGatewaySubscription ||
this.stopNavigationSubscription ||
@@ -443,13 +431,11 @@ class OpenClawShell extends LitElement {
this.updateGatewaySessionKey(context.gateway.snapshot);
this.updateGatewayStatus(context.gateway.snapshot);
this.updateTerminalSurface(context.gateway.snapshot);
- this.updateAgentLabel();
this.ensureRuntimeConfig(context.gateway.snapshot);
this.stopGatewaySubscription = context.gateway.subscribe((snapshot) => {
this.updateGatewaySessionKey(snapshot);
this.updateGatewayStatus(snapshot);
this.updateTerminalSurface(snapshot);
- this.updateAgentLabel();
this.ensureAgentsList(snapshot);
this.ensureRuntimeConfig(snapshot);
});
@@ -457,9 +443,6 @@ class OpenClawShell extends LitElement {
this.updateTerminalSurface(context.gateway.snapshot);
});
this.stopThemeSubscription = context.theme.subscribe(() => this.requestUpdate());
- this.stopAgentsSubscription = context.agents.subscribe(() => {
- this.updateAgentLabel();
- });
this.updateRouteState(selectShellRouteState(runtime.router.getState()));
this.stopRouteSubscription = runtime.router.subscribeSelector(
selectShellRouteState,
@@ -480,8 +463,7 @@ class OpenClawShell extends LitElement {
override disconnectedCallback() {
this.removeEventListener(COMMAND_PALETTE_TARGET_EVENT, this.handleCommandPaletteTarget);
- this.stopAgentsSubscription?.();
- this.stopAgentsSubscription = undefined;
+ document.removeEventListener("keydown", this.handleDocumentKeydown);
this.stopConfigSubscription?.();
this.stopConfigSubscription = undefined;
this.stopGatewaySubscription?.();
@@ -533,13 +515,23 @@ class OpenClawShell extends LitElement {
this.context?.replace("chat", this.chatNavigationOptions());
}
- private toggleNavDrawer(trigger: HTMLElement) {
- if (this.navDrawerOpen) {
- this.closeNavDrawer({ restoreFocus: true });
+ private toggleNavigationSurface(trigger?: HTMLElement) {
+ const context = this.context;
+ if (!context || this.onboarding) {
return;
}
- this.navDrawerTrigger = trigger;
- this.navDrawerOpen = true;
+ if (isMobileNavLayout()) {
+ if (this.navDrawerOpen) {
+ this.closeNavDrawer({ restoreFocus: Boolean(trigger) });
+ return;
+ }
+ this.navDrawerTrigger = trigger ?? null;
+ this.navDrawerOpen = true;
+ return;
+ }
+ context.navigation.update({
+ navCollapsed: !this.navCollapsed,
+ });
}
private closeNavDrawer(options: { restoreFocus?: boolean } = {}) {
@@ -564,6 +556,21 @@ class OpenClawShell extends LitElement {
this.closeNavDrawer({ restoreFocus: true });
};
+ private readonly handleDocumentKeydown = (event: KeyboardEvent) => {
+ if (
+ event.defaultPrevented ||
+ event.altKey ||
+ event.shiftKey ||
+ !event.metaKey ||
+ event.ctrlKey ||
+ event.key.toLowerCase() !== "b"
+ ) {
+ return;
+ }
+ event.preventDefault();
+ this.toggleNavigationSurface();
+ };
+
private readonly openPalette = () => {
this.commandPalette?.openPalette();
};
@@ -676,21 +683,9 @@ class OpenClawShell extends LitElement {
const sessionKey = new URLSearchParams(routeState.location?.search).get("session")?.trim();
if (sessionKey) {
this.activeSessionKey = sessionKey;
- this.updateAgentLabel();
}
}
- private updateAgentLabel() {
- const context = this.context;
- if (!context) {
- return;
- }
- this.agentLabel = resolveAgentLabel(
- this.activeSessionKey || context.gateway.snapshot.sessionKey,
- context.agents.state.agentsList,
- );
- }
-
private readonly updateNavigationPreferences = (
snapshot: ApplicationRuntime["context"]["navigation"]["snapshot"],
) => {
@@ -740,23 +735,12 @@ class OpenClawShell extends LitElement {
@click=${() => this.closeNavDrawer({ restoreFocus: true })}
>