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.
This commit is contained in:
Junyan Qin
2026-04-19 13:57:50 +08:00
committed by WangCham
parent eaffde0f89
commit 7858d17008
3 changed files with 22 additions and 0 deletions

View File

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

View File

@@ -131,6 +131,15 @@ export default function SystemStatusCard() {
{t('monitoring.pluginDisabled')}
</p>
)}
{pluginStatus &&
!pluginOk &&
pluginStatus.is_enable &&
pluginStatus.plugin_connector_error &&
pluginStatus.plugin_connector_error !== 'ok' && (
<p className="text-red-400 break-all">
{pluginStatus.plugin_connector_error}
</p>
)}
</div>
</div>
@@ -157,6 +166,11 @@ export default function SystemStatusCard() {
: t('monitoring.disconnected')}
</span>
</div>
{boxStatus && !boxOk && boxStatus.connector_error && (
<p className="text-red-400 break-all">
{boxStatus.connector_error}
</p>
)}
{boxStatus && (
<div className="text-muted-foreground space-y-0.5">
{boxStatus.backend && (

View File

@@ -355,6 +355,7 @@ export interface ApiRespBoxStatus {
available: boolean;
profile: string;
recent_error_count: number;
connector_error?: string;
backend?: {
name: string;
available: boolean;