refactor: remove proven dead code across runtime surfaces (#100823)

* refactor: remove unused internal helpers

* refactor(ui): remove unused compatibility helpers

* refactor(android): remove unused talk cleanup shim

* refactor(android): remove orphaned legacy UI

* refactor: remove unused private exports

* refactor(docs-i18n): remove dead matcher and satisfy lint
This commit is contained in:
Vincent Koc 2026-07-06 03:31:19 -07:00 committed by GitHub
parent 704c584686
commit 17777b3a9f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 22 additions and 1087 deletions

View file

@ -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,

View file

@ -167,7 +167,6 @@ class MainViewModel(
val mainSessionKey: StateFlow<String> = runtimeState(initial = "main") { it.mainSessionKey }
val cameraHud: StateFlow<CameraHudState?> = runtimeState(initial = null) { it.cameraHud }
val cameraFlashToken: StateFlow<Long> = runtimeState(initial = 0L) { it.cameraFlashToken }
val instanceId: StateFlow<String> = prefs.instanceId
val displayName: StateFlow<String> = prefs.displayName

View file

@ -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<CameraHudState?>(null)
val cameraHud: StateFlow<CameraHudState?> = _cameraHud.asStateFlow()
private val _cameraFlashToken = MutableStateFlow(0L)
val cameraFlashToken: StateFlow<Long> = _cameraFlashToken.asStateFlow()
private val _canvasA2uiHydrated = MutableStateFlow(false)
val canvasA2uiHydrated: StateFlow<Boolean> = _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,

View file

@ -28,7 +28,6 @@ class CameraHandler(
private val camera: CameraCaptureManager,
private val externalAudioCaptureActive: MutableStateFlow<Boolean>,
private val showCameraHud: (message: String, kind: CameraHudKind, autoHideMs: Long?) -> Unit,
private val triggerCameraFlash: () -> Unit,
private val invokeErrorFromThrowable: (err: Throwable) -> Pair<String, String>,
) {
/** 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()")

View file

@ -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),
)
}

View file

@ -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),
)
}
}
}
}
}
}
}

View file

@ -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,
)
}
}
}

View file

@ -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()
}
}
}

View file

@ -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)

View file

@ -346,7 +346,6 @@ class InvokeDispatcherTest {
camera = CameraCaptureManager(appContext),
externalAudioCaptureActive = MutableStateFlow(false),
showCameraHud = { _, _, _ -> },
triggerCameraFlash = {},
invokeErrorFromThrowable = { err -> "UNAVAILABLE" to (err.message ?: "camera failed") },
)
}

View file

@ -88,5 +88,3 @@ export function createPollCommentFolder(options?: { windowMs?: number }) {
},
};
}
export type PollCommentFolder = ReturnType<typeof createPollCommentFolder>;

View file

@ -327,17 +327,13 @@ export type QaScorecardEvidenceMode = z.infer<typeof qaScorecardEvidenceModeSche
export type QaScorecardChannelDriver = z.infer<typeof qaScorecardChannelDriverSchema>;
export type QaMaturityScoreKey = (typeof QA_MATURITY_SCORE_KEYS)[number];
export type QaMaturityScoreObject = z.infer<typeof qaMaturityScoreObjectSchema>;
export type QaMaturityScoreBundle = z.infer<typeof qaMaturityScoreBundleSchema>;
export type QaMaturityScoreLastRun = z.infer<typeof qaMaturityScoreLastRunSchema>;
export type QaMaturityScoreSurfaceLts = z.infer<typeof qaMaturityScoreSurfaceLtsSchema>;
export type QaMaturityScoreCategory = z.infer<typeof qaMaturityScoreCategorySchema>;
export type QaMaturityScoreSurface = z.infer<typeof qaMaturityScoreSurfaceSchema>;
export type QaMaturityScores = z.infer<typeof qaMaturityScoresSchema>;
export type QaMaturityTaxonomyLevel = z.infer<typeof qaMaturityLevelSchema>;
export type QaMaturityTaxonomyFeature = z.infer<typeof qaMaturityFeatureSchema>;
export type QaMaturityTaxonomyCategory = z.infer<typeof qaMaturityCategorySchema>;
export type QaMaturityTaxonomySurface = z.infer<typeof qaMaturitySurfaceSchema>;
export type QaMaturityTaxonomyProfile = z.infer<typeof qaScorecardProfileSchema>;
export type QaMaturityTaxonomy = z.infer<typeof qaMaturityTaxonomySchema>;
type QaCoverageEvidenceRole = z.infer<typeof qaCoverageEvidenceRoleSchema>;
@ -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<string, string[]> {
const profilesByCategory = new Map<string, string[]>();
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) {

View file

@ -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;

View file

@ -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;

View file

@ -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,

View file

@ -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*'([^']*)'`)

View file

@ -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
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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;
}

View file

@ -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<TranscriptRewriteResult> {
let sessionLock: Awaited<ReturnType<typeof acquireSessionWriteLock>> | 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();
}
}

View file

@ -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",

View file

@ -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})`;
}

View file

@ -207,8 +207,3 @@ export function abortQueuedChatTurns(
}
return runIds;
}
/** Test helper: clear all queued turns. */
export function clearAllQueuedChatTurns(chatQueuedTurns: QueuedChatTurnMap): void {
chatQueuedTurns.clear();
}

View file

@ -315,15 +315,6 @@ export function hasOnlySessionLifecycleMutationKindActive(
);
}
export async function waitForSessionLifecycleMutationIdle(
scope: string,
identities: Iterable<string | undefined>,
): Promise<void> {
await waitForNormalizedSessionLifecycleMutationIdle(
normalizeSessionIdentities(scope, identities),
);
}
export function isSessionWorkAdmissionActive(
scope: string,
identities: Iterable<string | undefined>,

View file

@ -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);
}

View file

@ -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
);
}

View file

@ -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 = [
{

View file

@ -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<RouteId>,
): Promise<void> {
return startApplicationRouter(router, history, basePath, context);
}
export {
APP_ROUTE_DEFINITIONS,
APP_ROUTE_IDS,

View file

@ -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<string, string>);
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,

View file

@ -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) {

View file

@ -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<string, PaletteItem[]>();
for (const item of items) {

View file

@ -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<ModelAuthStatusResult>("models.authStatus", params)) ?? EMPTY_AUTH_STATUS
);
}
export async function loadModelAuthStatusState(
state: ModelAuthStatusState,
opts?: { refresh?: boolean },
): Promise<void> {
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;
}
}

View file

@ -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<string, SessionsListResult["sessions"]>;
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<string, SessionsListResult["sessions"]>();
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<string, SessionsListResult["sessions"][number]>();
for (const row of rows) {
byKey.set(row.key, row);
}
const seenKeys = new Set<string>();
const groups = new Map<string, SessionOptionGroup>();
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<string, SessionOptionGroup>) {
for (const group of groups.values()) {
const counts = new Map<string, number>();
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<string, number>();
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;
}

View file

@ -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))
);
}