feat(services): enhance services with rollback and observability

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-08 00:12:08 +08:00
parent 36c93a764f
commit 74fdfc2652
5 changed files with 675 additions and 14 deletions

View File

@@ -4,6 +4,9 @@ import os
import platform
import socket
import subprocess
import time
import httpx
try:
import psutil
@@ -15,6 +18,10 @@ class SystemService:
_last_net_bytes_sent: int | None = None
_last_net_bytes_recv: int | None = None
_last_net_sample_at: float | None = None
_weather_cache: dict | None = None
_weather_cached_at: float | None = None
_weather_cached_location: str | None = None
_weather_cache_ttl_seconds: float = 10 * 60 # 10 minutes
def __init__(self):
# Import settings here to avoid circular imports
@@ -134,8 +141,95 @@ class SystemService:
'timestamp': datetime.now(UTC).isoformat(),
}
def get_config(self) -> dict:
async def _fetch_weather(self, location: str) -> dict:
try:
timeout = httpx.Timeout(10.0, connect=5.0)
async with httpx.AsyncClient(timeout=timeout) as client:
response = await client.get(f'https://wttr.in/{location}', params={'format': 'j1'})
response.raise_for_status()
payload = response.json()
current = (payload.get('current_condition') or [{}])[0]
weather_code = current.get('weatherCode')
temp = current.get('temp_C')
parsed_code = int(weather_code) if weather_code is not None and str(weather_code).isdigit() else None
if parsed_code is None or temp in (None, ''):
return {'weather_code': None, 'weather_summary': 'Weather unavailable'}
label = self._weather_code_label(parsed_code)
return {
'weather_code': parsed_code,
'weather_summary': f'{label} {temp}°C',
}
except (httpx.HTTPError, ValueError, TypeError):
return {'weather_code': None, 'weather_summary': 'Weather unavailable'}
@staticmethod
def _weather_code_label(code: int | None) -> str:
if code == 0:
return 'Clear'
if code in {1, 2}:
return 'Partly Cloudy'
if code == 3:
return 'Overcast'
if code in {45, 48}:
return 'Fog'
if code in {51, 53, 55, 56, 57}:
return 'Drizzle'
if code in {61, 63, 65, 66, 67, 80, 81, 82}:
return 'Rain'
if code in {71, 73, 75, 77, 85, 86}:
return 'Snow'
if code in {95, 96, 99}:
return 'Thunderstorm'
return 'Weather'
async def get_config(self) -> dict:
"""Get public system configuration."""
location = self._settings.LOCATION
now = time.time()
cached_weather = self.__class__._weather_cache
cached_at = self.__class__._weather_cached_at
cached_location = self.__class__._weather_cached_location
cache_is_valid = (
cached_weather is not None
and cached_at is not None
and cached_location == location
and (now - cached_at) < self.__class__._weather_cache_ttl_seconds
)
if cache_is_valid:
return {
'location': location,
**cached_weather,
'weather_cached': True,
'weather_cached_at': cached_at,
}
weather = await self._fetch_weather(location)
# If fetch failed but we have *any* last known weather for same location, return it to avoid UI flicker.
if (
(weather.get('weather_code') is None)
and cached_weather is not None
and cached_location == location
):
return {
'location': location,
**cached_weather,
'weather_cached': True,
'weather_cached_at': cached_at,
'weather_stale': True,
}
# Update cache on successful/meaningful payload (or keep "unavailable" if never succeeded).
self.__class__._weather_cache = weather
self.__class__._weather_cached_at = now
self.__class__._weather_cached_location = location
return {
'location': self._settings.LOCATION,
'location': location,
**weather,
'weather_cached': False,
'weather_cached_at': now,
}