From 7858d170087df63707477ddde4bb850c6474736c Mon Sep 17 00:00:00 2001
From: Junyan Qin
Date: Sun, 19 Apr 2026 13:57:50 +0800
Subject: [PATCH] feat: show connector error details for Plugin and Box runtime
status
Record Box connector error in BoxService and expose it as
'connector_error' in GET /api/v1/box/status when unavailable.
Display error messages in the dashboard System Status popover
for both Plugin Runtime (plugin_connector_error) and Box Runtime
(connector_error) when they are disconnected.
---
src/langbot/pkg/box/service.py | 7 +++++++
.../overview-cards/SystemStatusCards.tsx | 14 ++++++++++++++
web/src/app/infra/entities/api/index.ts | 1 +
3 files changed, 22 insertions(+)
diff --git a/src/langbot/pkg/box/service.py b/src/langbot/pkg/box/service.py
index 6c3bb74e..71f950f0 100644
--- a/src/langbot/pkg/box/service.py
+++ b/src/langbot/pkg/box/service.py
@@ -66,6 +66,7 @@ class BoxService:
self._recent_errors: collections.deque[dict] = collections.deque(maxlen=_MAX_RECENT_ERRORS)
self._shutdown_task = None
self._available = False
+ self._connector_error: str = ''
async def initialize(self):
self._ensure_default_host_workspace()
@@ -75,6 +76,7 @@ class BoxService:
else:
await self.client.initialize()
self._available = True
+ self._connector_error = ''
self.ap.logger.info(
f'LangBot Box runtime initialized: profile={self.profile.name} '
f'default_workspace={self.default_host_workspace or "(none)"}'
@@ -82,17 +84,21 @@ class BoxService:
except Exception as exc:
self.ap.logger.warning(f'LangBot Box runtime unavailable, sandbox features disabled: {exc}')
self._available = False
+ self._connector_error = str(exc)
async def _on_runtime_disconnect(self, connector: BoxRuntimeConnector) -> None:
"""Called by the connector when the Box runtime connection drops."""
self._available = False
+ self._connector_error = 'Disconnected from Box runtime'
self.ap.logger.warning('Box runtime disconnected, sandbox features temporarily disabled. Reconnecting in 3s...')
await asyncio.sleep(3)
try:
await connector.initialize()
self._available = True
+ self._connector_error = ''
self.ap.logger.info('Box runtime reconnected, sandbox features restored.')
except Exception as exc:
+ self._connector_error = str(exc)
self.ap.logger.warning(f'Box runtime reconnection failed: {exc}. Will retry on next heartbeat disconnect.')
@property
@@ -573,6 +579,7 @@ class BoxService:
'available': False,
'profile': self.profile.name,
'recent_error_count': len(self._recent_errors),
+ 'connector_error': self._connector_error,
}
runtime_status = await self.client.get_status()
return {
diff --git a/web/src/app/home/monitoring/components/overview-cards/SystemStatusCards.tsx b/web/src/app/home/monitoring/components/overview-cards/SystemStatusCards.tsx
index a69f6991..4eae8b55 100644
--- a/web/src/app/home/monitoring/components/overview-cards/SystemStatusCards.tsx
+++ b/web/src/app/home/monitoring/components/overview-cards/SystemStatusCards.tsx
@@ -131,6 +131,15 @@ export default function SystemStatusCard() {
{t('monitoring.pluginDisabled')}
)}
+ {pluginStatus &&
+ !pluginOk &&
+ pluginStatus.is_enable &&
+ pluginStatus.plugin_connector_error &&
+ pluginStatus.plugin_connector_error !== 'ok' && (
+
+ {pluginStatus.plugin_connector_error}
+
+ )}
@@ -157,6 +166,11 @@ export default function SystemStatusCard() {
: t('monitoring.disconnected')}
+ {boxStatus && !boxOk && boxStatus.connector_error && (
+
+ {boxStatus.connector_error}
+
+ )}
{boxStatus && (
{boxStatus.backend && (
diff --git a/web/src/app/infra/entities/api/index.ts b/web/src/app/infra/entities/api/index.ts
index d6896942..f259d1bc 100644
--- a/web/src/app/infra/entities/api/index.ts
+++ b/web/src/app/infra/entities/api/index.ts
@@ -355,6 +355,7 @@ export interface ApiRespBoxStatus {
available: boolean;
profile: string;
recent_error_count: number;
+ connector_error?: string;
backend?: {
name: string;
available: boolean;