diff --git a/CHANGELOG.md b/CHANGELOG.md index c395f054..1e6fe6a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ PyPI version — not the changelog header. ## [2026-07-21] +**feat(skills)** — telegram user_message_relay joins the sound layer: relay +events now carry their own sound key so an inbound user message is audible +like every other hook event (59/59 + 252 green). + **fix(devpulse)** — watchdog stall threshold 120s → 300s: the 120s no-JSONL-activity heuristic fired `[watchdog.stall]` on healthy agents doing long tool calls; 300s matches observed real-stall behavior (verified live diff --git a/src/aipass/skills/lib/telegram/apps/handlers/user_message_relay.py b/src/aipass/skills/lib/telegram/apps/handlers/user_message_relay.py index 4fd271ca..4bf7b307 100644 --- a/src/aipass/skills/lib/telegram/apps/handlers/user_message_relay.py +++ b/src/aipass/skills/lib/telegram/apps/handlers/user_message_relay.py @@ -1,9 +1,9 @@ # =================== AIPass ==================== # Name: user_message_relay.py # Description: Relay user messages from non-TG doors to the branch TG chat -# Version: 1.2.0 +# Version: 1.3.0 # Created: 2026-07-14 -# Modified: 2026-07-14 +# Modified: 2026-07-20 # ============================================= """ @@ -187,6 +187,7 @@ def handle(hook_data: dict) -> dict: if send_user_message(bot_token, chat_id, prompt): _last_relay_hash = msg_hash logger.info("[TG] user message relayed to chat_id=%s", chat_id) + return {"stdout": "", "exit_code": 0, "sound": "user message relay"} return {"stdout": "", "exit_code": 0} except Exception as e: diff --git a/src/aipass/skills/lib/telegram/tests/test_user_message_relay.py b/src/aipass/skills/lib/telegram/tests/test_user_message_relay.py index 3f4154d8..8e4fa52d 100644 --- a/src/aipass/skills/lib/telegram/tests/test_user_message_relay.py +++ b/src/aipass/skills/lib/telegram/tests/test_user_message_relay.py @@ -271,6 +271,24 @@ class TestHandle: assert result["exit_code"] == 0 mock_send.assert_called_once_with("123:FAKETOKEN", 42, "hello world") + def test_happy_path_has_sound_key(self, bot_dirs): + with patch.object(relay_mod, "send_user_message", return_value=True): + result = handle({"prompt": "hello world", "cwd": str(bot_dirs["work"])}) + assert result["sound"] == "user message relay" + + def test_send_failure_no_sound_key(self, bot_dirs): + with patch.object(relay_mod, "send_user_message", return_value=False): + result = handle({"prompt": "hello world", "cwd": str(bot_dirs["work"])}) + assert "sound" not in result + + def test_skips_no_bot_found_no_sound_key(self, tmp_path): + with ( + patch.object(relay_mod, "MIRROR_DIR", tmp_path / "nope1"), + patch.object(relay_mod, "PENDING_DIR", tmp_path / "nope2"), + ): + result = handle({"prompt": "hello", "cwd": "/tmp/nowhere"}) + assert "sound" not in result + def test_updates_dedup_hash_on_success(self, bot_dirs): with patch.object(relay_mod, "send_user_message", return_value=True): handle({"prompt": "hello", "cwd": str(bot_dirs["work"])})