Persist bot mode across restarts via database

Adds a BotSettings key-value table. The active mode is saved
when changed via /bcs-mode and restored on startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 09:26:00 -05:00
parent 622f0a325b
commit 6e1a73847d
3 changed files with 71 additions and 0 deletions

View File

@@ -126,6 +126,15 @@ class Database:
ALTER TABLE UserState ADD UserNotes NVARCHAR(MAX) NULL
""")
cursor.execute("""
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'BotSettings')
CREATE TABLE BotSettings (
SettingKey NVARCHAR(100) NOT NULL PRIMARY KEY,
SettingValue NVARCHAR(MAX) NULL,
UpdatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME()
)
""")
cursor.execute("""
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'LlmLog')
CREATE TABLE LlmLog (
@@ -414,6 +423,57 @@ class Database:
finally:
conn.close()
# ------------------------------------------------------------------
# Bot Settings (key-value store)
# ------------------------------------------------------------------
async def save_setting(self, key: str, value: str) -> None:
if not self._available:
return
try:
await asyncio.to_thread(self._save_setting_sync, key, value)
except Exception:
logger.exception("Failed to save setting %s", key)
def _save_setting_sync(self, key: str, value: str):
conn = self._connect()
try:
cursor = conn.cursor()
cursor.execute(
"""MERGE BotSettings AS target
USING (SELECT ? AS SettingKey) AS source
ON target.SettingKey = source.SettingKey
WHEN MATCHED THEN
UPDATE SET SettingValue = ?, UpdatedAt = SYSUTCDATETIME()
WHEN NOT MATCHED THEN
INSERT (SettingKey, SettingValue) VALUES (?, ?);""",
key, value, key, value,
)
cursor.close()
finally:
conn.close()
async def load_setting(self, key: str, default: str | None = None) -> str | None:
if not self._available:
return default
try:
return await asyncio.to_thread(self._load_setting_sync, key, default)
except Exception:
logger.exception("Failed to load setting %s", key)
return default
def _load_setting_sync(self, key: str, default: str | None) -> str | None:
conn = self._connect()
try:
cursor = conn.cursor()
cursor.execute(
"SELECT SettingValue FROM BotSettings WHERE SettingKey = ?", key
)
row = cursor.fetchone()
cursor.close()
return row[0] if row else default
finally:
conn.close()
async def close(self):
"""No persistent connection to close (connections are per-operation)."""
pass