diff --git a/apps/.i18n/native-source.json b/apps/.i18n/native-source.json index b3c8861cf01..a88fc5a7ad5 100644 --- a/apps/.i18n/native-source.json +++ b/apps/.i18n/native-source.json @@ -147,7 +147,7 @@ }, { "kind": "ui-state-text", - "line": 193, + "line": 192, "path": "apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt", "source": "Mic off", "surface": "android", @@ -155,7 +155,7 @@ }, { "kind": "ui-state-text", - "line": 203, + "line": 202, "path": "apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt", "source": "Off", "surface": "android", @@ -195,7 +195,7 @@ }, { "kind": "ui-state-text", - "line": 578, + "line": 577, "path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt", "source": "Offline", "surface": "android", @@ -203,7 +203,7 @@ }, { "kind": "conditional-branch", - "line": 2457, + "line": 2447, "path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt", "source": "Failed: this host requires wss:// or Tailscale Serve. No TLS endpoint detected.", "surface": "android", @@ -211,7 +211,7 @@ }, { "kind": "conditional-branch", - "line": 2459, + "line": 2449, "path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt", "source": "Failed: secure endpoint reached, but TLS fingerprint verification timed out. Check Tailscale Serve or gateway TLS and retry.", "surface": "android", @@ -219,7 +219,7 @@ }, { "kind": "conditional-branch", - "line": 2461, + "line": 2451, "path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt", "source": "Failed: couldn't reach the secure gateway endpoint for this host.", "surface": "android", @@ -2345,14 +2345,6 @@ "surface": "android", "id": "native.android.cf8344d3562e28e3" }, - { - "kind": "ui-named-argument", - "line": 271, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/PostOnboardingTabs.kt", - "source": "OpenClaw", - "surface": "android", - "id": "native.android.561d07951dc36959" - }, { "kind": "ui-named-argument", "line": 86, @@ -5657,14 +5649,6 @@ "surface": "android", "id": "native.android.994ac738d819f2e6" }, - { - "kind": "ui-named-argument", - "line": 96, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/design/ClawSurfaces.kt", - "source": "Needs attention", - "surface": "android", - "id": "native.android.75e509075f59deaa" - }, { "kind": "conditional-branch", "line": 221, diff --git a/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt b/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt index b820fa0d51b..d734ad20a61 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt @@ -167,7 +167,6 @@ class MainViewModel( val mainSessionKey: StateFlow = runtimeState(initial = "main") { it.mainSessionKey } val cameraHud: StateFlow = runtimeState(initial = null) { it.cameraHud } - val cameraFlashToken: StateFlow = runtimeState(initial = 0L) { it.cameraFlashToken } val instanceId: StateFlow = prefs.instanceId val displayName: StateFlow = prefs.displayName diff --git a/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt b/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt index ca851e02cfe..29a89365cb0 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt @@ -405,7 +405,6 @@ class NodeRuntime private constructor( camera = camera, externalAudioCaptureActive = externalAudioCaptureActive, showCameraHud = ::showCameraHud, - triggerCameraFlash = ::triggerCameraFlash, invokeErrorFromThrowable = { invokeErrorFromThrowable(it) }, ) @@ -599,9 +598,6 @@ class NodeRuntime private constructor( private val _cameraHud = MutableStateFlow(null) val cameraHud: StateFlow = _cameraHud.asStateFlow() - private val _cameraFlashToken = MutableStateFlow(0L) - val cameraFlashToken: StateFlow = _cameraFlashToken.asStateFlow() - private val _canvasA2uiHydrated = MutableStateFlow(false) val canvasA2uiHydrated: StateFlow = _canvasA2uiHydrated.asStateFlow() private val _canvasRehydratePending = MutableStateFlow(false) @@ -1944,12 +1940,6 @@ class NodeRuntime private constructor( } } - private fun finishTalkCaptureIfIdle(ownershipEpoch: Long) { - synchronized(voiceCaptureOwnershipLock) { - finishTalkCaptureIfIdleUnderOwnershipLock(ownershipEpoch) - } - } - private fun finishTalkCaptureIfIdleUnderOwnershipLock(ownershipEpoch: Long) { if (ownershipEpoch == 0L || voiceCaptureOwnershipEpoch.get() != ownershipEpoch) return if (!talkMode.isEnabled.value && !talkMode.isListening.value && !talkMode.isSpeaking.value) { @@ -3856,11 +3846,6 @@ class NodeRuntime private constructor( return trimmed.ifEmpty { null } } - private fun triggerCameraFlash() { - // Token is used as a pulse trigger; value doesn't matter as long as it changes. - _cameraFlashToken.value = SystemClock.elapsedRealtimeNanos() - } - private fun showCameraHud( message: String, kind: CameraHudKind, diff --git a/apps/android/app/src/main/java/ai/openclaw/app/node/CameraHandler.kt b/apps/android/app/src/main/java/ai/openclaw/app/node/CameraHandler.kt index 0411a9a6364..19a689c484a 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/node/CameraHandler.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/node/CameraHandler.kt @@ -28,7 +28,6 @@ class CameraHandler( private val camera: CameraCaptureManager, private val externalAudioCaptureActive: MutableStateFlow, private val showCameraHud: (message: String, kind: CameraHudKind, autoHideMs: Long?) -> Unit, - private val triggerCameraFlash: () -> Unit, private val invokeErrorFromThrowable: (err: Throwable) -> Pair, ) { /** Handles camera.list by exposing CameraX devices through gateway metadata. */ @@ -76,8 +75,6 @@ class CameraHandler( camLog("starting, params=$paramsJson") camLog("calling showCameraHud") showCameraHud("Taking photo…", CameraHudKind.Photo, null) - camLog("calling triggerCameraFlash") - triggerCameraFlash() val res = try { camLog("calling camera.snap()") diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/CameraHudOverlay.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/CameraHudOverlay.kt deleted file mode 100644 index 3410bed4b3f..00000000000 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/CameraHudOverlay.kt +++ /dev/null @@ -1,47 +0,0 @@ -package ai.openclaw.app.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.graphics.Color -import kotlinx.coroutines.delay - -/** Full-screen white flash keyed by camera capture tokens. */ -@Composable -fun CameraFlashOverlay( - token: Long, - modifier: Modifier = Modifier, -) { - Box(modifier = modifier.fillMaxSize()) { - CameraFlash(token = token) - } -} - -@Composable -private fun CameraFlash(token: Long) { - var alpha by remember { mutableFloatStateOf(0f) } - LaunchedEffect(token) { - if (token == 0L) return@LaunchedEffect - // Token changes replay the animation even when consecutive captures use - // the same HUD message. - alpha = 0.85f - delay(110) - alpha = 0f - } - - Box( - modifier = - Modifier - .fillMaxSize() - .alpha(alpha) - .background(Color.White), - ) -} diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/PostOnboardingTabs.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/PostOnboardingTabs.kt deleted file mode 100644 index 8ce53ebe2f2..00000000000 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/PostOnboardingTabs.kt +++ /dev/null @@ -1,364 +0,0 @@ -package ai.openclaw.app.ui - -import ai.openclaw.app.HomeDestination -import ai.openclaw.app.MainViewModel -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.ime -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawing -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ScreenShare -import androidx.compose.material.icons.filled.ChatBubble -import androidx.compose.material.icons.filled.CheckCircle -import androidx.compose.material.icons.filled.RecordVoiceOver -import androidx.compose.material.icons.filled.Settings -import androidx.compose.material3.Icon -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.zIndex - -private enum class HomeTab( - val label: String, - val icon: ImageVector, -) { - Connect(label = "Connect", icon = Icons.Default.CheckCircle), - Chat(label = "Chat", icon = Icons.Default.ChatBubble), - Voice(label = "Voice", icon = Icons.Default.RecordVoiceOver), - Screen(label = "Screen", icon = Icons.AutoMirrored.Filled.ScreenShare), - Settings(label = "Settings", icon = Icons.Default.Settings), -} - -private enum class StatusVisual { - Connected, - Connecting, - Warning, - Error, - Offline, -} - -/** Legacy tab scaffold used by the mobile post-onboarding experience. */ -@Composable -fun PostOnboardingTabs( - viewModel: MainViewModel, - modifier: Modifier = Modifier, -) { - var activeTab by rememberSaveable { mutableStateOf(HomeTab.Connect) } - var chatTabStarted by rememberSaveable { mutableStateOf(false) } - var screenTabStarted by rememberSaveable { mutableStateOf(false) } - val requestedHomeDestination by viewModel.requestedHomeDestination.collectAsState() - - LaunchedEffect(requestedHomeDestination) { - val destination = requestedHomeDestination ?: return@LaunchedEffect - activeTab = - when (destination) { - HomeDestination.Connect -> HomeTab.Connect - HomeDestination.Chat -> HomeTab.Chat - HomeDestination.Voice -> HomeTab.Voice - HomeDestination.Screen -> HomeTab.Screen - HomeDestination.Settings -> HomeTab.Settings - } - viewModel.clearRequestedHomeDestination() - } - - // Stop TTS when user navigates away from voice tab, and lazily keep the Chat/Screen tabs - // alive after the first visit so repeated tab switches do not rebuild their UI trees. - LaunchedEffect(activeTab) { - viewModel.setVoiceScreenActive(activeTab == HomeTab.Voice) - if (activeTab == HomeTab.Chat) { - chatTabStarted = true - } - if (activeTab == HomeTab.Screen) { - screenTabStarted = true - } - } - - val gatewayConnectionDisplay by viewModel.gatewayConnectionDisplay.collectAsState() - - val statusVisual = - remember(gatewayConnectionDisplay) { - val lower = gatewayConnectionDisplay.statusText.lowercase() - when { - gatewayConnectionDisplay.isConnected -> StatusVisual.Connected - lower.contains("connecting") || lower.contains("reconnecting") -> StatusVisual.Connecting - lower.contains("pairing") || lower.contains("approval") || lower.contains("auth") -> StatusVisual.Warning - lower.contains("error") || lower.contains("failed") -> StatusVisual.Error - else -> StatusVisual.Offline - } - } - - val density = LocalDensity.current - val imeVisible = WindowInsets.ime.getBottom(density) > 0 - val hideBottomTabBar = activeTab == HomeTab.Chat && imeVisible - - Scaffold( - modifier = modifier, - containerColor = Color.Transparent, - contentWindowInsets = WindowInsets(0, 0, 0, 0), - topBar = { - TopStatusBar( - statusText = gatewayConnectionDisplay.statusText, - statusVisual = statusVisual, - ) - }, - bottomBar = { - if (!hideBottomTabBar) { - BottomTabBar( - activeTab = activeTab, - onSelect = { activeTab = it }, - ) - } - }, - ) { innerPadding -> - Box( - modifier = - Modifier - .fillMaxSize() - .padding(innerPadding) - .consumeWindowInsets(innerPadding) - .background(mobileBackgroundGradient), - ) { - if (chatTabStarted) { - // Keep chat mounted after first use so session state and scroll position - // survive tab switches. - Box( - modifier = - Modifier - .matchParentSize() - .alpha(if (activeTab == HomeTab.Chat) 1f else 0f) - .zIndex(if (activeTab == HomeTab.Chat) 1f else 0f), - ) { - ChatSheet(viewModel = viewModel) - } - } - - if (screenTabStarted) { - // Canvas can be expensive to initialize; keep it mounted once visited - // and hide it by alpha/z-order instead of destroying the view tree. - ScreenTabScreen( - viewModel = viewModel, - visible = activeTab == HomeTab.Screen, - modifier = - Modifier - .matchParentSize() - .alpha(if (activeTab == HomeTab.Screen) 1f else 0f) - .zIndex(if (activeTab == HomeTab.Screen) 1f else 0f), - ) - } - - when (activeTab) { - HomeTab.Connect -> ConnectTabScreen(viewModel = viewModel) - HomeTab.Chat -> if (!chatTabStarted) ChatSheet(viewModel = viewModel) - HomeTab.Voice -> VoiceTabScreen(viewModel = viewModel) - HomeTab.Screen -> Unit - HomeTab.Settings -> SettingsSheet(viewModel = viewModel) - } - } - } -} - -/** Screen tab wrapper that refreshes canvas data once per gateway connection. */ -@Composable -private fun ScreenTabScreen( - viewModel: MainViewModel, - visible: Boolean, - modifier: Modifier = Modifier, -) { - val isConnected by viewModel.isConnected.collectAsState() - var refreshedForCurrentConnection by rememberSaveable(isConnected) { mutableStateOf(false) } - - LaunchedEffect(isConnected, visible, refreshedForCurrentConnection) { - if (visible && isConnected && !refreshedForCurrentConnection) { - viewModel.refreshHomeCanvasOverviewIfConnected() - refreshedForCurrentConnection = true - } - } - - Box(modifier = modifier.fillMaxSize()) { - CanvasScreen(viewModel = viewModel, visible = visible, modifier = Modifier.fillMaxSize()) - } -} - -/** Top status chip derived from gateway connection text. */ -@Composable -private fun TopStatusBar( - statusText: String, - statusVisual: StatusVisual, -) { - val safeInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) - - val (chipBg, chipDot, chipText, chipBorder) = - when (statusVisual) { - StatusVisual.Connected -> - listOf( - mobileSuccessSoft, - mobileSuccess, - mobileSuccess, - LocalMobileColors.current.chipBorderConnected, - ) - StatusVisual.Connecting -> - listOf( - mobileAccentSoft, - mobileAccent, - mobileAccent, - LocalMobileColors.current.chipBorderConnecting, - ) - StatusVisual.Warning -> - listOf( - mobileWarningSoft, - mobileWarning, - mobileWarning, - LocalMobileColors.current.chipBorderWarning, - ) - StatusVisual.Error -> - listOf( - mobileDangerSoft, - mobileDanger, - mobileDanger, - LocalMobileColors.current.chipBorderError, - ) - StatusVisual.Offline -> - listOf( - mobileSurface, - mobileTextTertiary, - mobileTextSecondary, - mobileBorder, - ) - } - - Surface( - modifier = Modifier.fillMaxWidth().windowInsetsPadding(safeInsets), - color = Color.Transparent, - shadowElevation = 0.dp, - ) { - Row( - modifier = Modifier.fillMaxWidth().padding(horizontal = 18.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - ) { - Text( - text = "OpenClaw", - style = mobileTitle2, - color = mobileText, - ) - Surface( - shape = RoundedCornerShape(999.dp), - color = chipBg, - border = androidx.compose.foundation.BorderStroke(1.dp, chipBorder), - ) { - Row( - modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp), - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Surface( - modifier = Modifier.padding(top = 1.dp), - color = chipDot, - shape = RoundedCornerShape(999.dp), - ) { - Box(modifier = Modifier.padding(4.dp)) - } - Text( - text = statusText.trim().ifEmpty { "Offline" }, - style = mobileCaption1, - color = chipText, - maxLines = 1, - ) - } - } - } - } -} - -/** Bottom navigation for the legacy tab scaffold. */ -@Composable -private fun BottomTabBar( - activeTab: HomeTab, - onSelect: (HomeTab) -> Unit, -) { - val safeInsets = WindowInsets.navigationBars.only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal) - - Box( - modifier = - Modifier - .fillMaxWidth(), - ) { - Surface( - modifier = Modifier.fillMaxWidth(), - color = mobileCardSurface.copy(alpha = 0.97f), - shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp), - border = BorderStroke(1.dp, mobileBorder), - shadowElevation = 6.dp, - ) { - Row( - modifier = - Modifier - .fillMaxWidth() - .windowInsetsPadding(safeInsets) - .padding(horizontal = 10.dp, vertical = 10.dp), - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - HomeTab.entries.forEach { tab -> - val active = tab == activeTab - Surface( - onClick = { onSelect(tab) }, - modifier = Modifier.weight(1f).heightIn(min = 58.dp), - shape = RoundedCornerShape(16.dp), - color = if (active) mobileAccentSoft else Color.Transparent, - border = if (active) BorderStroke(1.dp, LocalMobileColors.current.chipBorderConnecting) else null, - shadowElevation = 0.dp, - ) { - Column( - modifier = Modifier.fillMaxWidth().padding(horizontal = 6.dp, vertical = 7.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(2.dp), - ) { - Icon( - imageVector = tab.icon, - contentDescription = tab.label, - tint = if (active) mobileAccent else mobileTextTertiary, - ) - Text( - text = tab.label, - color = if (active) mobileAccent else mobileTextSecondary, - style = mobileCaption2.copy(fontWeight = if (active) FontWeight.Bold else FontWeight.Medium), - ) - } - } - } - } - } - } -} diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/TalkOrbOverlay.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/TalkOrbOverlay.kt deleted file mode 100644 index 18945e1ea90..00000000000 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/TalkOrbOverlay.kt +++ /dev/null @@ -1,137 +0,0 @@ -package ai.openclaw.app.ui - -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.tween -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp - -/** - * Full-screen talk-mode presence indicator with pulsing rings and status fallback text. - */ -@Composable -fun TalkOrbOverlay( - seamColor: Color, - statusText: String, - isListening: Boolean, - isSpeaking: Boolean, - modifier: Modifier = Modifier, -) { - val transition = rememberInfiniteTransition(label = "talk-orb") - val t by - transition.animateFloat( - initialValue = 0f, - targetValue = 1f, - animationSpec = - infiniteRepeatable( - animation = tween(durationMillis = 1500, easing = LinearEasing), - repeatMode = RepeatMode.Restart, - ), - label = "pulse", - ) - - val trimmed = statusText.trim() - val showStatus = trimmed.isNotEmpty() && trimmed != "Off" - val phase = - when { - isSpeaking -> "Speaking" - isListening -> "Listening" - else -> "Thinking" - } - - Column( - modifier = modifier.padding(24.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(12.dp), - ) { - Box(contentAlignment = Alignment.Center) { - Canvas(modifier = Modifier.size(360.dp)) { - val center = this.center - val baseRadius = size.minDimension * 0.30f - - val ring1 = 1.05f + (t * 0.25f) - val ring2 = 1.20f + (t * 0.55f) - val ringAlpha1 = (1f - t) * 0.34f - val ringAlpha2 = (1f - t) * 0.22f - - drawCircle( - color = seamColor.copy(alpha = ringAlpha1), - radius = baseRadius * ring1, - center = center, - style = Stroke(width = 3.dp.toPx()), - ) - drawCircle( - color = seamColor.copy(alpha = ringAlpha2), - radius = baseRadius * ring2, - center = center, - style = Stroke(width = 3.dp.toPx()), - ) - - drawCircle( - brush = - Brush.radialGradient( - colors = - listOf( - seamColor.copy(alpha = 0.92f), - seamColor.copy(alpha = 0.40f), - Color.Black.copy(alpha = 0.56f), - ), - center = center, - radius = baseRadius * 1.35f, - ), - radius = baseRadius, - center = center, - ) - - drawCircle( - color = seamColor.copy(alpha = 0.34f), - radius = baseRadius, - center = center, - style = Stroke(width = 1.dp.toPx()), - ) - } - } - - if (showStatus) { - Surface( - color = Color.Black.copy(alpha = 0.40f), - shape = CircleShape, - ) { - Text( - text = trimmed, - modifier = Modifier.padding(horizontal = 14.dp, vertical = 8.dp), - color = Color.White.copy(alpha = 0.92f), - style = MaterialTheme.typography.labelLarge, - fontWeight = FontWeight.SemiBold, - ) - } - } else { - Text( - text = phase, - color = Color.White.copy(alpha = 0.80f), - style = MaterialTheme.typography.labelLarge, - fontWeight = FontWeight.SemiBold, - ) - } - } -} diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/design/ClawSurfaces.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/design/ClawSurfaces.kt index f9f71a9dc18..17d4221539a 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/design/ClawSurfaces.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/design/ClawSurfaces.kt @@ -80,23 +80,3 @@ internal fun ClawLoadingState( } } } - -/** - * Shared recoverable error block with the app's attention styling. - */ -@Composable -internal fun ClawErrorState( - title: String, - body: String, - modifier: Modifier = Modifier, - action: (@Composable () -> Unit)? = null, -) { - ClawPanel(modifier = modifier) { - Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) { - ClawStatusPill(text = "Needs attention", status = ClawStatus.Danger) - Text(text = title, style = ClawTheme.type.section, color = ClawTheme.colors.text) - Text(text = body, style = ClawTheme.type.body, color = ClawTheme.colors.textMuted) - action?.invoke() - } - } -} diff --git a/apps/android/app/src/test/java/ai/openclaw/app/GatewayBootstrapAuthTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/GatewayBootstrapAuthTest.kt index c258eee10bb..752dbc3922b 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/GatewayBootstrapAuthTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/GatewayBootstrapAuthTest.kt @@ -984,7 +984,7 @@ class GatewayBootstrapAuthTest { ownershipEpoch.set(41L) runtime.setMicEnabled(true) - val cleanup = runtime.javaClass.getDeclaredMethod("finishTalkCaptureIfIdle", Long::class.javaPrimitiveType) + val cleanup = runtime.javaClass.getDeclaredMethod("cleanupFailedTalkCapture", Long::class.javaPrimitiveType) cleanup.isAccessible = true cleanup.invoke(runtime, 41L) diff --git a/apps/android/app/src/test/java/ai/openclaw/app/node/InvokeDispatcherTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/node/InvokeDispatcherTest.kt index 34438c35810..553ff8bbe1e 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/node/InvokeDispatcherTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/node/InvokeDispatcherTest.kt @@ -346,7 +346,6 @@ class InvokeDispatcherTest { camera = CameraCaptureManager(appContext), externalAudioCaptureActive = MutableStateFlow(false), showCameraHud = { _, _, _ -> }, - triggerCameraFlash = {}, invokeErrorFromThrowable = { err -> "UNAVAILABLE" to (err.message ?: "camera failed") }, ) } diff --git a/extensions/imessage/src/monitor/poll-comment.ts b/extensions/imessage/src/monitor/poll-comment.ts index 3b3cdd1511a..492eb5dd2b3 100644 --- a/extensions/imessage/src/monitor/poll-comment.ts +++ b/extensions/imessage/src/monitor/poll-comment.ts @@ -88,5 +88,3 @@ export function createPollCommentFolder(options?: { windowMs?: number }) { }, }; } - -export type PollCommentFolder = ReturnType; diff --git a/extensions/qa-lab/src/scorecard-taxonomy.ts b/extensions/qa-lab/src/scorecard-taxonomy.ts index 33cd6008896..a39f92dd4c9 100644 --- a/extensions/qa-lab/src/scorecard-taxonomy.ts +++ b/extensions/qa-lab/src/scorecard-taxonomy.ts @@ -327,17 +327,13 @@ export type QaScorecardEvidenceMode = z.infer; export type QaMaturityScoreKey = (typeof QA_MATURITY_SCORE_KEYS)[number]; export type QaMaturityScoreObject = z.infer; -export type QaMaturityScoreBundle = z.infer; -export type QaMaturityScoreLastRun = z.infer; export type QaMaturityScoreSurfaceLts = z.infer; export type QaMaturityScoreCategory = z.infer; export type QaMaturityScoreSurface = z.infer; export type QaMaturityScores = z.infer; export type QaMaturityTaxonomyLevel = z.infer; -export type QaMaturityTaxonomyFeature = z.infer; export type QaMaturityTaxonomyCategory = z.infer; export type QaMaturityTaxonomySurface = z.infer; -export type QaMaturityTaxonomyProfile = z.infer; export type QaMaturityTaxonomy = z.infer; type QaCoverageEvidenceRole = z.infer; @@ -603,23 +599,6 @@ export function qaMaturityTaxonomyLevelMap(taxonomy: QaMaturityTaxonomy) { return new Map(taxonomy.levels.map((level) => [level.id, level])); } -export function qaMaturityCategoryProfiles(taxonomy: QaMaturityTaxonomy): Map { - const profilesByCategory = new Map(); - for (const profile of taxonomy.profiles) { - const categoryIds = profile.includeAllCategories - ? activeQaMaturityTaxonomySurfaces(taxonomy).flatMap((surface) => - surface.categories.map((category) => `${surface.id}.${category.id}`), - ) - : profile.categoryIds; - for (const categoryId of categoryIds) { - const profiles = profilesByCategory.get(categoryId) ?? []; - profiles.push(profile.id); - profilesByCategory.set(categoryId, profiles); - } - } - return profilesByCategory; -} - export function qaMaturityFamilyOrder(surfaces: readonly QaMaturityTaxonomySurface[]): string[] { const seen: string[] = []; for (const surface of surfaces) { diff --git a/extensions/telegram/src/telegram-ingress-spool.ts b/extensions/telegram/src/telegram-ingress-spool.ts index 210c577387f..5f17f272c59 100644 --- a/extensions/telegram/src/telegram-ingress-spool.ts +++ b/extensions/telegram/src/telegram-ingress-spool.ts @@ -237,12 +237,6 @@ export function isTelegramSpooledUpdateClaimOwnedByOtherLiveProcess( ); } -export function isTelegramSpooledUpdateClaimOwnedByCurrentProcess( - claim: ClaimedTelegramSpooledUpdate, -): boolean { - return claim.claim?.processId === TELEGRAM_SPOOLED_UPDATE_PROCESS_ID; -} - export async function writeTelegramSpooledUpdate(params: { spoolDir: string; update: unknown; diff --git a/packages/acp-core/src/runtime/session-identifiers.ts b/packages/acp-core/src/runtime/session-identifiers.ts index a7bb7625ceb..75b8ef662d5 100644 --- a/packages/acp-core/src/runtime/session-identifiers.ts +++ b/packages/acp-core/src/runtime/session-identifiers.ts @@ -58,20 +58,6 @@ function resolveAcpAgentResumeHintLine(params: { return resolver ? resolver({ agentSessionId }) : undefined; } -/** Renders status-safe ACP session identifier lines from persisted session metadata. */ -export function resolveAcpSessionIdentifierLines(params: { - sessionKey: string; - meta?: SessionAcpMeta; -}): string[] { - const backend = normalizeText(params.meta?.backend) ?? "backend"; - const identity = resolveSessionIdentityFromMeta(params.meta); - return resolveAcpSessionIdentifierLinesFromIdentity({ - backend, - identity, - mode: "status", - }); -} - /** Renders resolved ACP backend/agent ids, hiding pending ids from thread intros. */ export function resolveAcpSessionIdentifierLinesFromIdentity(params: { backend: string; diff --git a/scripts/docs-i18n/doc_chunked_raw.go b/scripts/docs-i18n/doc_chunked_raw.go index 42455d954aa..ea7a7c354a6 100644 --- a/scripts/docs-i18n/doc_chunked_raw.go +++ b/scripts/docs-i18n/doc_chunked_raw.go @@ -14,10 +14,7 @@ import ( const defaultDocChunkMaxBytes = 12000 const defaultDocChunkPromptBudget = 15000 -var ( - docsFenceRE = regexp.MustCompile(`^\s*(` + "```" + `|~~~)`) - docsComponentTagRE = regexp.MustCompile(`<(/?)([A-Z][A-Za-z0-9]*)\b[^>]*?/?>`) -) +var docsComponentTagRE = regexp.MustCompile(`<(/?)([A-Z][A-Za-z0-9]*)\b[^>]*?/?>`) var docsProtocolTokens = []string{ frontmatterTagStart, diff --git a/scripts/docs-i18n/localized_links.go b/scripts/docs-i18n/localized_links.go index ebe1e523666..2c777c82ad7 100644 --- a/scripts/docs-i18n/localized_links.go +++ b/scripts/docs-i18n/localized_links.go @@ -30,7 +30,7 @@ type docsRedirect struct { var ( localeDirRe = regexp.MustCompile(`^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})?$`) fencedBacktickCodeBlock = regexp.MustCompile("(?ms)(^|\\n)[ \\t]*```[^\\n]*\\n.*?\\n[ \\t]*```[ \\t]*(?:\\n|$)") - fencedTildeCodeBlock = regexp.MustCompile("(?ms)(^|\\n)[ \\t]*~~~[^\\n]*\\n.*?\\n[ \\t]*~~~[ \\t]*(?:\\n|$)") + fencedTildeCodeBlock = regexp.MustCompile(`(?ms)(^|\n)[ \t]*~~~[^\n]*\n.*?\n[ \t]*~~~[ \t]*(?:\n|$)`) markdownLinkTargetRe = regexp.MustCompile(`!?\[[^\]]*\]\(([^)]+)\)`) hrefDoubleQuotedValueRe = regexp.MustCompile(`\bhref\s*=\s*"([^"]*)"`) hrefSingleQuotedValueRe = regexp.MustCompile(`\bhref\s*=\s*'([^']*)'`) diff --git a/scripts/docs-i18n/process.go b/scripts/docs-i18n/process.go index b598354e6c9..f4a42ec7ef0 100644 --- a/scripts/docs-i18n/process.go +++ b/scripts/docs-i18n/process.go @@ -114,9 +114,7 @@ func splitFrontMatter(content string) (string, string) { } front := strings.Join(lines[1:endIndex], "\n") body := strings.Join(lines[endIndex+1:], "\n") - if strings.HasPrefix(body, "\n") { - body = body[1:] - } + body = strings.TrimPrefix(body, "\n") return front, body } diff --git a/scripts/docs-i18n/tm.go b/scripts/docs-i18n/tm.go index fa89766450e..83bffb6ef0c 100644 --- a/scripts/docs-i18n/tm.go +++ b/scripts/docs-i18n/tm.go @@ -40,7 +40,9 @@ func LoadTranslationMemory(path string) (*TranslationMemory, error) { } return nil, err } - defer file.Close() + defer func() { + _ = file.Close() + }() reader := bufio.NewReader(file) for { diff --git a/scripts/docs-i18n/translator.go b/scripts/docs-i18n/translator.go index f97767d58a3..b9872575da5 100644 --- a/scripts/docs-i18n/translator.go +++ b/scripts/docs-i18n/translator.go @@ -207,7 +207,9 @@ func runCodexExecPrompt(ctx context.Context, req codexPromptRequest) (string, er } outputPath := outputFile.Name() _ = outputFile.Close() - defer os.Remove(outputPath) + defer func() { + _ = os.Remove(outputPath) + }() codexHomeBase, err := isolatedCodexHomeBase() if err != nil { @@ -217,7 +219,9 @@ func runCodexExecPrompt(ctx context.Context, req codexPromptRequest) (string, er if err != nil { return "", err } - defer os.RemoveAll(codexHome) + defer func() { + _ = os.RemoveAll(codexHome) + }() if err := writeCodexAuthFile(codexHome); err != nil { return "", err } diff --git a/src/agents/compaction-planning.ts b/src/agents/compaction-planning.ts index 7fe647cbc5a..66e0808babd 100644 --- a/src/agents/compaction-planning.ts +++ b/src/agents/compaction-planning.ts @@ -74,12 +74,12 @@ export function sanitizeCompactionMessages(messages: AgentMessage[]): AgentMessa } /** Estimates one message using the same sanitization path as multi-message planning. */ -export function estimateCompactionMessageTokens(message: AgentMessage): number { +function estimateCompactionMessageTokens(message: AgentMessage): number { return estimateMessagesTokens([message]); } /** Clamps requested split parts to a usable count for the available messages. */ -export function normalizeCompactionParts(parts: number, messageCount: number): number { +function normalizeCompactionParts(parts: number, messageCount: number): number { if (!Number.isFinite(parts) || parts <= 1) { return 1; } diff --git a/src/agents/embedded-agent-runner/transcript-rewrite.ts b/src/agents/embedded-agent-runner/transcript-rewrite.ts index f003f517998..2b244c934d5 100644 --- a/src/agents/embedded-agent-runner/transcript-rewrite.ts +++ b/src/agents/embedded-agent-runner/transcript-rewrite.ts @@ -18,7 +18,6 @@ import { import { SessionManager } from "../sessions/index.js"; import { log } from "./logger.js"; import { - persistTranscriptStateMutation, readTranscriptFileState, type TranscriptFileState, type TranscriptPersistedEntry, @@ -491,71 +490,3 @@ export async function rewriteTranscriptEntriesInRuntimeTranscript(params: { await sessionLock?.release(); } } - -/** - * Rewrites a named transcript file artifact. Runtime callers should prefer - * rewriteTranscriptEntriesInRuntimeTranscript with agent/session scope. - */ -export async function rewriteTranscriptEntriesInSessionFile(params: { - sessionFile: string; - sessionId?: string; - sessionKey?: string; - agentId?: string; - request: TranscriptRewriteRequest; - config?: SessionWriteLockAcquireTimeoutConfig; -}): Promise { - let sessionLock: Awaited> | undefined; - try { - sessionLock = await acquireSessionWriteLock({ - sessionFile: params.sessionFile, - ...resolveSessionWriteLockOptions(params.config), - }); - const state = await readTranscriptFileState(params.sessionFile); - const result = rewriteTranscriptEntriesInState({ - state, - replacements: params.request.replacements, - ...(params.request.allowedRewriteSuffixEntryIds - ? { allowedRewriteSuffixEntryIds: params.request.allowedRewriteSuffixEntryIds } - : {}), - }); - if (result.changed) { - await persistTranscriptStateMutation({ - sessionFile: params.sessionFile, - state, - appendedEntries: result.appendedEntries, - }); - emitSessionTranscriptUpdate({ - sessionFile: params.sessionFile, - sessionKey: params.sessionKey, - ...(params.agentId ? { agentId: params.agentId } : {}), - ...(params.sessionId && params.sessionKey && params.agentId - ? { - target: { - agentId: params.agentId, - sessionId: params.sessionId, - sessionKey: params.sessionKey, - }, - } - : {}), - }); - log.info( - `[transcript-rewrite] rewrote ${result.rewrittenEntries} entr` + - `${result.rewrittenEntries === 1 ? "y" : "ies"} ` + - `bytesFreed=${result.bytesFreed} ` + - `sessionKey=${params.sessionKey ?? params.sessionId ?? "unknown"}`, - ); - } - return result; - } catch (err) { - const reason = formatErrorMessage(err); - log.warn(`[transcript-rewrite] failed: ${reason}`); - return { - changed: false, - bytesFreed: 0, - rewrittenEntries: 0, - reason, - }; - } finally { - await sessionLock?.release(); - } -} diff --git a/src/auto-reply/reply/abort.ts b/src/auto-reply/reply/abort.ts index 0f7b46d6a73..dde0b7fc5f7 100644 --- a/src/auto-reply/reply/abort.ts +++ b/src/auto-reply/reply/abort.ts @@ -138,10 +138,6 @@ export function abortSessionRunTargetWithOutcome(params: { key?: string; session return { active, aborted }; } -export function abortSessionRunTarget(params: { key?: string; sessionId?: string }): boolean { - return abortSessionRunTargetWithOutcome(params).aborted; -} - export function formatAbortReplyText( stoppedSubagents?: number, rejectionReason?: "finalizing", diff --git a/src/commands/onboard-inference.ts b/src/commands/onboard-inference.ts index abbc0155766..a630d5496eb 100644 --- a/src/commands/onboard-inference.ts +++ b/src/commands/onboard-inference.ts @@ -190,8 +190,3 @@ export async function detectInferenceBackends( ); return candidates; } - -/** Format a candidate for prompts/logs, e.g. "Codex — openai/gpt-5.5 (logged in)". */ -export function formatInferenceBackendCandidate(candidate: InferenceBackendCandidate): string { - return `${candidate.label} — ${candidate.modelRef} (${candidate.detail})`; -} diff --git a/src/gateway/chat-queued-turns.ts b/src/gateway/chat-queued-turns.ts index ff7531fc7e4..11ba265b1f9 100644 --- a/src/gateway/chat-queued-turns.ts +++ b/src/gateway/chat-queued-turns.ts @@ -207,8 +207,3 @@ export function abortQueuedChatTurns( } return runIds; } - -/** Test helper: clear all queued turns. */ -export function clearAllQueuedChatTurns(chatQueuedTurns: QueuedChatTurnMap): void { - chatQueuedTurns.clear(); -} diff --git a/src/sessions/session-lifecycle-admission.ts b/src/sessions/session-lifecycle-admission.ts index 58d2c6f7a8d..782ea6fe88c 100644 --- a/src/sessions/session-lifecycle-admission.ts +++ b/src/sessions/session-lifecycle-admission.ts @@ -315,15 +315,6 @@ export function hasOnlySessionLifecycleMutationKindActive( ); } -export async function waitForSessionLifecycleMutationIdle( - scope: string, - identities: Iterable, -): Promise { - await waitForNormalizedSessionLifecycleMutationIdle( - normalizeSessionIdentities(scope, identities), - ); -} - export function isSessionWorkAdmissionActive( scope: string, identities: Iterable, diff --git a/src/skills/research/signals.ts b/src/skills/research/signals.ts index f185f4ca759..8b0cc4f3093 100644 --- a/src/skills/research/signals.ts +++ b/src/skills/research/signals.ts @@ -293,10 +293,3 @@ export function extractDurableInstructionProposals(params: { maxProposals: params.maxProposals, }); } - -/** Extracts a single candidate durable instruction; kept for callers that want one proposal. */ -export function extractDurableInstructionProposal(params: { - messages: unknown[]; -}): DurableInstruction | undefined { - return extractDurableInstructionProposals({ ...params, maxProposals: 1 }).at(-1); -} diff --git a/src/tasks/codex-native-subagent-task.ts b/src/tasks/codex-native-subagent-task.ts index e626ae2c6f5..63676596aea 100644 --- a/src/tasks/codex-native-subagent-task.ts +++ b/src/tasks/codex-native-subagent-task.ts @@ -1,20 +1,5 @@ -// Runs Codex native subagent tasks and maps their lifecycle into task registry state. -import { - isChildlessNativeSubagentTask, - resolveChildlessNativeSubagentTaskDefinition, -} from "./native-subagent-task.js"; -import type { TaskRecord } from "./task-registry.types.js"; - /** Runtime label used for Codex-native subagent task records. */ export const CODEX_NATIVE_SUBAGENT_RUNTIME = "subagent"; export const CODEX_NATIVE_SUBAGENT_TASK_KIND = "codex-native"; export const CODEX_NATIVE_SUBAGENT_RUN_ID_PREFIX = "codex-thread:"; export const CODEX_NATIVE_SUBAGENT_STALE_ERROR = "Codex native subagent stopped reporting progress"; - -/** Detects native Codex subagent tasks that have no child session to recover from. */ -export function isChildlessCodexNativeSubagentTask(task: TaskRecord): boolean { - return ( - isChildlessNativeSubagentTask(task) && - resolveChildlessNativeSubagentTaskDefinition(task)?.taskKind === CODEX_NATIVE_SUBAGENT_TASK_KIND - ); -} diff --git a/src/tasks/native-subagent-task.ts b/src/tasks/native-subagent-task.ts index 8c033b2efdf..56c11272de7 100644 --- a/src/tasks/native-subagent-task.ts +++ b/src/tasks/native-subagent-task.ts @@ -4,8 +4,6 @@ import type { TaskRecord } from "./task-registry.types.js"; export const COPILOT_NATIVE_SUBAGENT_TASK_KIND = "copilot-native"; export const COPILOT_NATIVE_SUBAGENT_RUN_ID_PREFIX = "copilot-agent:"; -export const COPILOT_NATIVE_SUBAGENT_STALE_ERROR = - "Copilot native subagent stopped reporting progress"; const CHILDLESS_NATIVE_SUBAGENT_DEFINITIONS = [ { diff --git a/ui/src/app-routes.ts b/ui/src/app-routes.ts index 0a68cdbde00..36c7c1f1596 100644 --- a/ui/src/app-routes.ts +++ b/ui/src/app-routes.ts @@ -80,15 +80,6 @@ export async function startApplicationRouter( await router.start(history, basePath, context); } -export function startAppRouter( - router: ApplicationRouter, - history: RouterHistory, - basePath: string, - context: ApplicationContext, -): Promise { - return startApplicationRouter(router, history, basePath, context); -} - export { APP_ROUTE_DEFINITIONS, APP_ROUTE_IDS, diff --git a/ui/src/app/assistant-identity.ts b/ui/src/app/assistant-identity.ts index 928e8a896eb..fbad806b7a7 100644 --- a/ui/src/app/assistant-identity.ts +++ b/ui/src/app/assistant-identity.ts @@ -69,30 +69,6 @@ export function loadLocalAssistantIdentity(opts?: { } } -export function saveLocalAssistantIdentity(next: LocalAssistantIdentity) { - const agentId = normalizeOptionalString(next.agentId); - if (!agentId) { - return; - } - const storage = getSafeLocalStorage(); - try { - const raw = storage?.getItem(LOCAL_ASSISTANT_IDENTITY_KEY); - const avatars = raw - ? parseLocalAssistantAvatarMap(raw).avatars - : (Object.create(null) as Record); - const avatar = normalizeOptionalString(next.avatar); - if (avatar) { - avatars[agentId] = avatar; - } else { - delete avatars[agentId]; - } - persistLocalAssistantAvatarMap(storage, avatars); - } catch { - // best-effort — quota exceeded or security restrictions should not - // prevent in-memory identity updates from being applied - } -} - export async function fetchAssistantIdentity( client: GatewayBrowserClient, sessionKey?: string, diff --git a/ui/src/components/app-sidebar.ts b/ui/src/components/app-sidebar.ts index 4f1a56b1eb7..ed64039f323 100644 --- a/ui/src/components/app-sidebar.ts +++ b/ui/src/components/app-sidebar.ts @@ -271,7 +271,6 @@ export class AppSidebar extends LitElement { agentsList: context.agents.state.agentsList, chatAgentSessionRowsByAgent: this.sessionRowsByAgent, sessionsResult: this.sessionsResult, - sessionsResultAgentId: this.sessionsAgentId, sessionKey: routeSessionKey, }, nextAgentId, @@ -768,7 +767,6 @@ export class AppSidebar extends LitElement { const options = resolveSessionAgentFilterOptions({ agentsList: this.context?.agents.state.agentsList, sessionsResult: this.sessionsResult, - sessionsResultAgentId: this.sessionsAgentId, sessionKey, }); if (options.length <= 1) { diff --git a/ui/src/components/command-palette.ts b/ui/src/components/command-palette.ts index 137a57398b1..d751e35bf1d 100644 --- a/ui/src/components/command-palette.ts +++ b/ui/src/components/command-palette.ts @@ -82,10 +82,6 @@ function getPaletteItemsInternal(): PaletteItem[] { return getPaletteBaseItems(); } -export function getPaletteItems(): readonly PaletteItem[] { - return getPaletteItemsInternal(); -} - type CommandPaletteProps = { open: boolean; query: string; @@ -112,10 +108,6 @@ function filteredItems(query: string, includeSlashCommands = true): PaletteItem[ ); } -export function getFilteredPaletteItems(query: string): readonly PaletteItem[] { - return filteredItems(query); -} - function groupItems(items: PaletteItem[]): Array<[string, PaletteItem[]]> { const map = new Map(); for (const item of items) { diff --git a/ui/src/lib/model-auth.ts b/ui/src/lib/model-auth.ts index 1212a5584fa..587cfff34a5 100644 --- a/ui/src/lib/model-auth.ts +++ b/ui/src/lib/model-auth.ts @@ -4,14 +4,6 @@ import type { ModelAuthStatusProvider, ModelAuthStatusResult } from "../api/type const EMPTY_AUTH_STATUS: ModelAuthStatusResult = { ts: 0, providers: [] }; -export type ModelAuthStatusState = { - client: GatewayBrowserClient | null; - connected: boolean; - modelAuthStatusLoading: boolean; - modelAuthStatusResult: ModelAuthStatusResult | null; - modelAuthStatusError: string | null; -}; - /** * True when a provider's auth should be actively monitored on the dashboard. * @@ -46,34 +38,3 @@ export async function loadModelAuthStatus( (await client.request("models.authStatus", params)) ?? EMPTY_AUTH_STATUS ); } - -export async function loadModelAuthStatusState( - state: ModelAuthStatusState, - opts?: { refresh?: boolean }, -): Promise { - const client = state.client; - if (!client || !state.connected) { - state.modelAuthStatusLoading = false; - return; - } - if (state.modelAuthStatusLoading) { - return; - } - state.modelAuthStatusLoading = true; - state.modelAuthStatusError = null; - try { - const result = await loadModelAuthStatus(client, opts); - if (state.client !== client || !state.connected) { - return; - } - state.modelAuthStatusResult = result; - } catch (err) { - if (state.client !== client || !state.connected) { - return; - } - state.modelAuthStatusError = err instanceof Error ? err.message : String(err); - state.modelAuthStatusResult = EMPTY_AUTH_STATUS; - } finally { - state.modelAuthStatusLoading = false; - } -} diff --git a/ui/src/lib/sessions/session-options.ts b/ui/src/lib/sessions/session-options.ts index 64dcc7e2897..aa45ad71d64 100644 --- a/ui/src/lib/sessions/session-options.ts +++ b/ui/src/lib/sessions/session-options.ts @@ -1,7 +1,6 @@ import type { SessionsListResult } from "../../api/types.ts"; -import { isCronSessionKey, resolveSessionDisplayName } from "../session-display.ts"; +import { isCronSessionKey } from "../session-display.ts"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "../string-coerce.ts"; -import { getVisibleSessionRows } from "./index.ts"; import { buildAgentMainSessionKey, isSessionKeyTiedToAgent, @@ -20,23 +19,10 @@ type SessionAgentOptionsState = { }> | null; } | null; chatAgentSessionRowsByAgent?: Record; - sessionsHideCron?: boolean; sessionsResult?: SessionsListResult | null; - sessionsResultAgentId?: string | null; sessionKey: string; }; -export type SessionOptionGroup = { - id: string; - label: string; - options: Array<{ - key: string; - label: string; - scopeLabel: string; - title: string; - }>; -}; - export type SessionAgentFilterOption = { id: string; label: string; @@ -64,35 +50,6 @@ function resolvePreferredSessionCandidateAgentId( return normalizeAgentId(parsed?.agentId ?? defaultAgentId); } -export function rememberSessionAgentRows( - state: SessionAgentOptionsState, - sessions: SessionsListResult | null, -): void { - if (!sessions) { - return; - } - const refreshedAgentId = normalizeOptionalString(state.sessionsResultAgentId); - const defaultAgentId = normalizeAgentId(state.agentsList?.defaultId ?? "main"); - const grouped = new Map(); - for (const row of sessions.sessions) { - const agentId = resolvePreferredSessionCandidateAgentId(row, defaultAgentId); - if (!agentId) { - continue; - } - grouped.set(agentId, [...(grouped.get(agentId) ?? []), row]); - } - if (grouped.size === 0 && !refreshedAgentId) { - return; - } - state.chatAgentSessionRowsByAgent ??= {}; - if (refreshedAgentId) { - state.chatAgentSessionRowsByAgent[refreshedAgentId] = grouped.get(refreshedAgentId) ?? []; - } - for (const [agentId, agentRows] of grouped) { - state.chatAgentSessionRowsByAgent[agentId] = agentRows; - } -} - function rowsForPreferredAgentSession( state: SessionAgentOptionsState, normalizedAgentId: string, @@ -165,153 +122,6 @@ export function resolveSessionAgentFilterOptions( return options; } -export function resolveSessionOptionGroups( - state: SessionAgentOptionsState, - sessionKey: string, - sessions: SessionsListResult | null, -): SessionOptionGroup[] { - const rows = sessions?.sessions ?? []; - const hideCron = state.sessionsHideCron ?? true; - const activeAgentId = resolveSessionAgentFilterId(state, sessionKey); - const defaultAgentId = normalizeAgentId(state.agentsList?.defaultId ?? "main"); - const byKey = new Map(); - for (const row of rows) { - byKey.set(row.key, row); - } - - const seenKeys = new Set(); - const groups = new Map(); - const ensureGroup = (groupId: string, label: string): SessionOptionGroup => { - const existing = groups.get(groupId); - if (existing) { - return existing; - } - const created: SessionOptionGroup = { - id: groupId, - label, - options: [], - }; - groups.set(groupId, created); - return created; - }; - - const addOption = (key: string) => { - if (!key || seenKeys.has(key)) { - return; - } - seenKeys.add(key); - const row = byKey.get(key); - const parsed = parseAgentSessionKey(key); - const group = parsed - ? ensureGroup( - `agent:${normalizeLowercaseStringOrEmpty(parsed.agentId)}`, - resolveAgentGroupLabel(state, parsed.agentId), - ) - : ensureGroup("other", "Other Sessions"); - const scopeLabel = normalizeOptionalString(parsed?.rest) ?? key; - group.options.push({ - key, - label: resolveSessionScopedOptionLabel(key, row, parsed?.rest), - scopeLabel, - title: key, - }); - }; - - for (const row of getVisibleSessionRows(sessions, { - currentSessionKey: sessionKey, - agentId: activeAgentId, - defaultAgentId, - filterByAgent: true, - hideCron, - })) { - addOption(row.key); - } - if (byKey.has(sessionKey)) { - addOption(sessionKey); - } else if (sessionKey) { - addOption(sessionKey); - } - - disambiguateSessionOptionLabels(groups); - return Array.from(groups.values()); -} - -function disambiguateSessionOptionLabels(groups: Map) { - for (const group of groups.values()) { - const counts = new Map(); - for (const option of group.options) { - counts.set(option.label, (counts.get(option.label) ?? 0) + 1); - } - for (const option of group.options) { - if ((counts.get(option.label) ?? 0) > 1 && option.scopeLabel !== option.label) { - option.label = `${option.label} · ${option.scopeLabel}`; - } - } - } - - const allOptions = Array.from(groups.values()).flatMap((group) => - group.options.map((option) => ({ groupLabel: group.label, option })), - ); - const labels = new Map(allOptions.map(({ option }) => [option, option.label])); - const countAssignedLabels = () => { - const counts = new Map(); - for (const { option } of allOptions) { - const label = labels.get(option) ?? option.label; - counts.set(label, (counts.get(label) ?? 0) + 1); - } - return counts; - }; - const labelIncludesScopeLabel = (label: string, scopeLabel: string) => { - const trimmedScope = scopeLabel.trim(); - if (!trimmedScope) { - return false; - } - return ( - label === trimmedScope || - label.endsWith(` · ${trimmedScope}`) || - label.endsWith(` / ${trimmedScope}`) - ); - }; - - const globalCounts = countAssignedLabels(); - for (const { groupLabel, option } of allOptions) { - const currentLabel = labels.get(option) ?? option.label; - if ((globalCounts.get(currentLabel) ?? 0) <= 1) { - continue; - } - const scopedPrefix = `${groupLabel} / `; - if (currentLabel.startsWith(scopedPrefix)) { - continue; - } - labels.set(option, `${groupLabel} / ${currentLabel}`); - } - - const scopedCounts = countAssignedLabels(); - for (const { option } of allOptions) { - const currentLabel = labels.get(option) ?? option.label; - if ((scopedCounts.get(currentLabel) ?? 0) <= 1) { - continue; - } - if (labelIncludesScopeLabel(currentLabel, option.scopeLabel)) { - continue; - } - labels.set(option, `${currentLabel} · ${option.scopeLabel}`); - } - - const finalCounts = countAssignedLabels(); - for (const { option } of allOptions) { - const currentLabel = labels.get(option) ?? option.label; - if ((finalCounts.get(currentLabel) ?? 0) <= 1) { - continue; - } - labels.set(option, `${currentLabel} · ${option.key}`); - } - - for (const { option } of allOptions) { - option.label = labels.get(option) ?? option.label; - } -} - function resolveAgentGroupLabel(state: SessionAgentOptionsState, agentIdRaw: string): string { const normalized = normalizeLowercaseStringOrEmpty(agentIdRaw); const agent = (state.agentsList?.agents ?? []).find( @@ -321,22 +131,3 @@ function resolveAgentGroupLabel(state: SessionAgentOptionsState, agentIdRaw: str normalizeOptionalString(agent?.identity?.name) ?? normalizeOptionalString(agent?.name) ?? ""; return name && name !== agentIdRaw ? `${name} (${agentIdRaw})` : agentIdRaw; } - -function resolveSessionScopedOptionLabel( - key: string, - row?: SessionsListResult["sessions"][number], - rest?: string, -) { - const base = normalizeOptionalString(rest) ?? key; - if (!row) { - return base; - } - - const label = normalizeOptionalString(row.label) ?? ""; - const displayName = normalizeOptionalString(row.displayName) ?? ""; - if ((label && label !== key) || (displayName && displayName !== key)) { - return resolveSessionDisplayName(key, row); - } - - return base; -} diff --git a/ui/src/pages/chat/chat-queue.ts b/ui/src/pages/chat/chat-queue.ts index 60c2c926e3e..5513e478eda 100644 --- a/ui/src/pages/chat/chat-queue.ts +++ b/ui/src/pages/chat/chat-queue.ts @@ -222,15 +222,3 @@ export function markQueuedChatSendsWaitingForReconnect(host: ChatQueueStoreHost) host.chatQueueBySession = queueBySession; } } - -export function hasReconnectableQueuedChatSends(host: ChatQueueStoreHost): boolean { - const matches = (item: ChatQueueItem) => - Boolean(item.sendRunId) && - item.sendState === "waiting-reconnect" && - !item.pendingRunId && - !item.localCommandName; - return ( - host.chatQueue.some(matches) || - Object.values(host.chatQueueBySession ?? {}).some((queue) => queue.some(matches)) - ); -}