@@ -9,6 +9,7 @@ import "../components/app-topbar.ts";
99import "../components/exec-approval.ts" ;
1010import "../components/gateway-url-confirmation.ts" ;
1111import "../components/login-gate.ts" ;
12+ import "../components/terminal/terminal-panel.ts" ;
1213import "../components/tooltip.ts" ;
1314import "../components/update-banner.ts" ;
1415import { APP_ROUTE_IDS , isRouteId , pathForRoute , type RouteId } from "../app-routes.ts" ;
@@ -18,6 +19,7 @@ import {
1819 type CommandPaletteTargetDetail ,
1920} from "../components/command-palette.ts" ;
2021import type { ThemeModeChangeDetail } from "../components/theme-mode-toggle.ts" ;
22+ import { isGatewayMethodAdvertised } from "../lib/gateway-methods.ts" ;
2123import { searchForSession } from "../lib/sessions/index.ts" ;
2224import { resolveAgentIdFromSessionKey } from "../lib/sessions/session-key.ts" ;
2325import { normalizeLowercaseStringOrEmpty , normalizeOptionalString } from "../lib/string-coerce.ts" ;
@@ -27,6 +29,7 @@ import {
2729 type ApplicationContext ,
2830 type ApplicationNavigationOptions ,
2931} from "./context.ts" ;
32+ import { hasOperatorAdminAccess } from "./operator-access.ts" ;
3033import type { ApplicationOverlaySnapshot } from "./overlays.ts" ;
3134import { selectRenderedRouteMatch } from "./router-outlet.ts" ;
3235
@@ -71,6 +74,23 @@ function resolveOnboardingMode(): boolean {
7174 return raw !== null && / ^ (?: 1 | t r u e | y e s | o n ) $ / iu. test ( raw . trim ( ) ) ;
7275}
7376
77+ function resolveTerminalThemeMode ( ) : "dark" | "light" {
78+ return document . documentElement . dataset . themeMode === "light" ? "light" : "dark" ;
79+ }
80+
81+ function isTerminalAvailable (
82+ snapshot : ApplicationContext [ "gateway" ] [ "snapshot" ] ,
83+ terminalEnabled : boolean ,
84+ ) : boolean {
85+ if ( ! snapshot . connected || ! terminalEnabled ) {
86+ return false ;
87+ }
88+ return (
89+ hasOperatorAdminAccess ( snapshot . hello ?. auth ?? null ) &&
90+ isGatewayMethodAdvertised ( snapshot , "terminal.open" ) === true
91+ ) ;
92+ }
93+
7494export class OpenClawApp extends LitElement {
7595 @state ( ) private gatewayConnected = false ;
7696 @state ( ) private gatewayLastError : string | null = null ;
@@ -232,6 +252,8 @@ class OpenClawShell extends LitElement {
232252 @state ( ) private recentSessionsCollapsed = false ;
233253 @state ( ) private navDrawerOpen = false ;
234254 @state ( ) private gatewayConnected = false ;
255+ @state ( ) private terminalAvailable = false ;
256+ @state ( ) private terminalClient : GatewayBrowserClient | null = null ;
235257 @state ( ) private activeSessionKey = "" ;
236258 @state ( ) private agentLabel = "" ;
237259 @state ( ) private routeState : ShellRouteState = { } ;
@@ -249,10 +271,12 @@ class OpenClawShell extends LitElement {
249271 private agentsListClient : GatewayBrowserClient | null = null ;
250272 private sessionKeyClient : GatewayBrowserClient | null = null ;
251273 private stopAgentsSubscription : ( ( ) => void ) | undefined ;
274+ private stopConfigSubscription : ( ( ) => void ) | undefined ;
252275 private stopGatewaySubscription : ( ( ) => void ) | undefined ;
253276 private stopNavigationSubscription : ( ( ) => void ) | undefined ;
254277 private stopRouteSubscription : ( ( ) => void ) | undefined ;
255278 private stopOverlaySubscription : ( ( ) => void ) | undefined ;
279+ private stopThemeSubscription : ( ( ) => void ) | undefined ;
256280
257281 override createRenderRoot ( ) {
258282 return this ;
@@ -275,10 +299,12 @@ class OpenClawShell extends LitElement {
275299 ! runtime ||
276300 ! context ||
277301 this . stopAgentsSubscription ||
302+ this . stopConfigSubscription ||
278303 this . stopGatewaySubscription ||
279304 this . stopNavigationSubscription ||
280305 this . stopRouteSubscription ||
281- this . stopOverlaySubscription
306+ this . stopOverlaySubscription ||
307+ this . stopThemeSubscription
282308 ) {
283309 return ;
284310 }
@@ -288,13 +314,19 @@ class OpenClawShell extends LitElement {
288314 } ) ;
289315 this . updateGatewaySessionKey ( context . gateway . snapshot ) ;
290316 this . updateGatewayStatus ( context . gateway . snapshot ) ;
317+ this . updateTerminalSurface ( context . gateway . snapshot ) ;
291318 this . updateAgentLabel ( ) ;
292319 this . stopGatewaySubscription = context . gateway . subscribe ( ( snapshot ) => {
293320 this . updateGatewaySessionKey ( snapshot ) ;
294321 this . updateGatewayStatus ( snapshot ) ;
322+ this . updateTerminalSurface ( snapshot ) ;
295323 this . updateAgentLabel ( ) ;
296324 this . ensureAgentsList ( snapshot ) ;
297325 } ) ;
326+ this . stopConfigSubscription = context . config . subscribe ( ( ) => {
327+ this . updateTerminalSurface ( context . gateway . snapshot ) ;
328+ } ) ;
329+ this . stopThemeSubscription = context . theme . subscribe ( ( ) => this . requestUpdate ( ) ) ;
298330 this . stopAgentsSubscription = context . agents . subscribe ( ( ) => {
299331 this . updateAgentLabel ( ) ;
300332 } ) ;
@@ -316,6 +348,8 @@ class OpenClawShell extends LitElement {
316348 this . removeEventListener ( COMMAND_PALETTE_TARGET_EVENT , this . handleCommandPaletteTarget ) ;
317349 this . stopAgentsSubscription ?.( ) ;
318350 this . stopAgentsSubscription = undefined ;
351+ this . stopConfigSubscription ?.( ) ;
352+ this . stopConfigSubscription = undefined ;
319353 this . stopGatewaySubscription ?.( ) ;
320354 this . stopGatewaySubscription = undefined ;
321355 this . stopNavigationSubscription ?.( ) ;
@@ -324,8 +358,11 @@ class OpenClawShell extends LitElement {
324358 this . stopRouteSubscription = undefined ;
325359 this . stopOverlaySubscription ?.( ) ;
326360 this . stopOverlaySubscription = undefined ;
361+ this . stopThemeSubscription ?.( ) ;
362+ this . stopThemeSubscription = undefined ;
327363 this . agentsListClient = null ;
328364 this . sessionKeyClient = null ;
365+ this . terminalClient = null ;
329366 this . navDrawerTrigger = null ;
330367 super . disconnectedCallback ( ) ;
331368 }
@@ -429,6 +466,14 @@ class OpenClawShell extends LitElement {
429466 this . gatewayConnected = snapshot . connected ;
430467 } ;
431468
469+ private updateTerminalSurface ( snapshot : ApplicationContext [ "gateway" ] [ "snapshot" ] ) {
470+ this . terminalClient = snapshot . connected ? snapshot . client : null ;
471+ this . terminalAvailable = isTerminalAvailable (
472+ snapshot ,
473+ this . context ?. config . current . terminalEnabled ?? false ,
474+ ) ;
475+ }
476+
432477 private ensureAgentsList ( snapshot : { client : GatewayBrowserClient | null ; connected : boolean } ) {
433478 if ( ! snapshot . connected || ! snapshot . client ) {
434479 this . agentsListClient = null ;
@@ -449,11 +494,11 @@ class OpenClawShell extends LitElement {
449494 client : GatewayBrowserClient | null ;
450495 sessionKey : string ;
451496 } ) {
452- if ( snapshot . client === this . sessionKeyClient && this . activeSessionKey ) {
497+ const sessionKey = snapshot . sessionKey . trim ( ) ;
498+ if ( snapshot . client === this . sessionKeyClient && sessionKey === this . activeSessionKey ) {
453499 return ;
454500 }
455501 this . sessionKeyClient = snapshot . client ;
456- const sessionKey = snapshot . sessionKey . trim ( ) ;
457502 if ( sessionKey ) {
458503 this . activeSessionKey = sessionKey ;
459504 }
@@ -533,6 +578,9 @@ class OpenClawShell extends LitElement {
533578 .themeMode=${ context . theme . mode }
534579 .onboarding=${ this . onboarding }
535580 .onOpenPalette=${ this . openPalette }
581+ .terminalAvailable=${ this . terminalAvailable }
582+ .onToggleTerminal=${ ( ) =>
583+ window . dispatchEvent ( new CustomEvent ( "openclaw:terminal-toggle" ) ) }
536584 .onToggleDrawer=${ ( trigger : HTMLElement ) => this . toggleNavDrawer ( trigger ) }
537585 .onNavigate=${ ( routeId : string , options ?: ApplicationNavigationOptions ) =>
538586 this . navigate ( routeId , options ) }
@@ -593,6 +641,11 @@ class OpenClawShell extends LitElement {
593641 .onNotFound=${ ( ) => this . replaceChatWithCurrentSession ( ) }
594642 > </ openclaw-router-outlet >
595643 </ main >
644+ < openclaw-terminal-panel
645+ .client =${ this . terminalClient }
646+ .available =${ this . terminalAvailable }
647+ .themeMode=${ resolveTerminalThemeMode ( ) }
648+ > </ openclaw-terminal-panel >
596649 < openclaw-exec-approval
597650 .props =${ {
598651 queue : this . overlaySnapshot . approvalQueue ,
0 commit comments