mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-07 02:51:50 +00:00
fix(telegram): polish topic mode — CASCADE, General-topic handling, rename guard, debounce
Five follow-ups to topic mode based on integration audit: 1. ON DELETE CASCADE on telegram_dm_topic_bindings.session_id. Session pruning (manual /delete, auto-cleanup, any future prune job) would have thrown 'FOREIGN KEY constraint failed' for sessions bound to a topic. Migration bumped to v2, rebuilds the bindings table in place if FK lacks CASCADE. Idempotent; only runs once per DB. 2. Never auto-rename operator-declared topics. If an operator has extra.dm_topics configured AND a user runs /topic, messages in those pre-declared topics would previously trigger auto-rename and silently mutate operator config. _rename_telegram_topic_for_session_title now early-returns when _get_dm_topic_info returns a dict for this (chat_id, thread_id). Uses class-based lookup (not hasattr) so MagicMock test fixtures don't accidentally trip the guard. 3. General topic handling. Telegram's General (pinned top) topic in a forum-enabled private chat may send messages with message_thread_id=1 or omit thread_id entirely depending on client. Both are now treated as the root lobby, not a topic lane. Prevents users from accidentally burning a session on the General topic. 4. Debounce the root-lobby reminder. 30-second cooldown per chat so a user who forgets topic mode is enabled and types ten messages in the root gets one reminder, not ten. Explicit command replies (/new-in-lobby, /topic <session-id>) still land every time. 5. Docs: added under-the-hood invariants for the above, plus a Downgrade section explaining that rolling back to a pre-/topic Hermes build leaves the DB tables orphaned but harmless — DMs just revert to native per-thread isolation. Tests: - test_operator_declared_topic_is_not_auto_renamed - test_general_topic_is_treated_as_root_lobby - test_lobby_reminder_is_debounced_per_chat - test_binding_survives_session_deletion_via_cascade - test_migration_rebuilds_v1_binding_table_with_cascade_fk Validated: 4803/4804 tests pass (tests/gateway/ + tests/test_hermes_state.py). Sole failure is a pre-existing test_teams::test_send_typing flake unrelated to this PR.
This commit is contained in:
parent
1a9542cf75
commit
1381c89e56
5 changed files with 291 additions and 21 deletions
|
|
@ -2155,6 +2155,11 @@ class SessionDB:
|
|||
reconciliation. Operators must be able to upgrade Hermes, keep the old
|
||||
Telegram bot behavior running, and only mutate topic-mode state when the
|
||||
user executes /topic to opt into the feature.
|
||||
|
||||
Schema versions:
|
||||
v1 — initial shape (no ON DELETE CASCADE on session_id FK)
|
||||
v2 — session_id FK gets ON DELETE CASCADE so session pruning
|
||||
automatically clears bindings.
|
||||
"""
|
||||
def _do(conn):
|
||||
conn.executescript(
|
||||
|
|
@ -2177,7 +2182,7 @@ class SessionDB:
|
|||
thread_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
session_key TEXT NOT NULL,
|
||||
session_id TEXT NOT NULL REFERENCES sessions(id),
|
||||
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
||||
managed_mode TEXT NOT NULL DEFAULT 'auto',
|
||||
linked_at REAL NOT NULL,
|
||||
updated_at REAL NOT NULL,
|
||||
|
|
@ -2191,10 +2196,55 @@ class SessionDB:
|
|||
ON telegram_dm_topic_bindings(user_id, chat_id);
|
||||
"""
|
||||
)
|
||||
|
||||
# v1 → v2: rebuild telegram_dm_topic_bindings if its session_id FK
|
||||
# lacks ON DELETE CASCADE. SQLite can't ALTER a foreign key, so we
|
||||
# rebuild the table. Only runs once per DB (version gate).
|
||||
current = conn.execute(
|
||||
"SELECT value FROM state_meta WHERE key = ?",
|
||||
("telegram_dm_topic_schema_version",),
|
||||
).fetchone()
|
||||
current_version = int(current[0]) if current and str(current[0]).isdigit() else 0
|
||||
if current_version < 2:
|
||||
fk_rows = conn.execute(
|
||||
"PRAGMA foreign_key_list('telegram_dm_topic_bindings')"
|
||||
).fetchall()
|
||||
needs_rebuild = any(
|
||||
row[2] == "sessions" and (row[6] or "") != "CASCADE"
|
||||
for row in fk_rows
|
||||
)
|
||||
if needs_rebuild:
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE telegram_dm_topic_bindings_new (
|
||||
chat_id TEXT NOT NULL,
|
||||
thread_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
session_key TEXT NOT NULL,
|
||||
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
||||
managed_mode TEXT NOT NULL DEFAULT 'auto',
|
||||
linked_at REAL NOT NULL,
|
||||
updated_at REAL NOT NULL,
|
||||
PRIMARY KEY (chat_id, thread_id)
|
||||
);
|
||||
INSERT INTO telegram_dm_topic_bindings_new
|
||||
SELECT chat_id, thread_id, user_id, session_key,
|
||||
session_id, managed_mode, linked_at, updated_at
|
||||
FROM telegram_dm_topic_bindings;
|
||||
DROP TABLE telegram_dm_topic_bindings;
|
||||
ALTER TABLE telegram_dm_topic_bindings_new
|
||||
RENAME TO telegram_dm_topic_bindings;
|
||||
CREATE UNIQUE INDEX idx_telegram_dm_topic_bindings_session
|
||||
ON telegram_dm_topic_bindings(session_id);
|
||||
CREATE INDEX idx_telegram_dm_topic_bindings_user
|
||||
ON telegram_dm_topic_bindings(user_id, chat_id);
|
||||
"""
|
||||
)
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO state_meta (key, value) VALUES (?, ?) "
|
||||
"ON CONFLICT(key) DO UPDATE SET value = excluded.value",
|
||||
("telegram_dm_topic_schema_version", "1"),
|
||||
("telegram_dm_topic_schema_version", "2"),
|
||||
)
|
||||
self._execute_write(_do)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue