feat(telegram): voice-out via ElevenLabs TTS + shared TTS foundation (#25)#1397
Merged
Conversation
Establishes epic #24's shared outbound-voice foundation and wires Telegram as the first consumer: - services/tts_service.py — shared ElevenLabs client + ffmpeg MP3→OGG/Opus transcode + shared char cost-cap; fail-soft everywhere (any error → text). - Shared agent-level config (no per-channel sprawl): agent_ownership .tts_voice_replies_enabled + .tts_voice_id, via TtsMixin. Dual-track migration (SQLite + Alembic 0011). GET/PUT /api/agents/{name}/voice-replies (owner-only). - telegram_adapter: _send_voice (sendVoice) + a voice branch in send_response that speaks the reply when enabled, falling back to text on any miss. - ffmpeg added to the backend image; ELEVENLABS_API_KEY/ELEVENLABS_MODEL_ID/ TTS_MAX_CHARS in config + .env.example. - UI: voice-replies toggle + ElevenLabs voice-id field in the Telegram panel. - Tests: tts_service (cost-cap/no-key/provider-error/success, transcode + ffmpeg-missing, end-to-end) and the adapter voice branch (16 tests). Trigger policy is Always-when-enabled (Mirror is infeasible — Telegram voice-in isn't implemented). Slack (#26) and WhatsApp (trinity-enterprise#56) reuse this shared layer unchanged. Related to Abilityai/trinity-enterprise#25 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Resolve by running |
3 tasks
7 tasks
…ests (#762) The `lint (sys.modules pollution check)` CI job failed on tests/unit/test_25_telegram_voice_out.py: two bare `sys.modules[...]` assignments (the import-time `utils.helpers` preload and the `database` stub in the `adapter` fixture) are new violations vs the #762 baseline. Adopt the sanctioned snapshot/restore pattern (precedent: test_telegram_webhook_backfill.py) — a top-level `_STUBBED_MODULE_NAMES` list + an autouse `_restore_sys_modules` fixture — which both satisfies the lint and adds the real per-test restore hygiene #762 is about, so the stubs can't leak across files. No test behavior change (16 tests still pass). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The three new config vars were read by src/backend/config.py but wired into neither docker-compose.yml nor docker-compose.prod.yml backend.environment:, so the .env lever was inert on deploy — voice replies could never be enabled (the #1056 VOIP_* / trinity-enterprise#31 LOG_* packaging class, caught by /validate-pr step 4.9). Non-empty defaults for MODEL_ID per the #1076 pattern. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
PR Validation Report (/validate-pr)PR: #1397 — feat(telegram): voice-out via ElevenLabs TTS + shared TTS foundation
Recommendation: APPROVE (after the on-branch packaging fix)Suggestions (non-blocking):
|
vybe
pushed a commit
that referenced
this pull request
Jul 2, 2026
/#1335/#1406 conflicts Resolutions: - requirements.md: take dev's index (#1406 split); relocate the #1205 section into requirements/public-access.md as Section 47 (46 was taken by #1335's roster requirement) - Alembic: renumber 0011_agent_ownership_public_channel_prompt -> 0012, re-parent on 0011_agent_ownership_tts_voice (#1397 landed first; whichever lands second renumbers) - db/migrations.py: keep both MIGRATIONS entries (tts_voice + public_channel_prompt) - routers/sharing.py + SharingPanel.vue + architecture.md: keep both #1335 roster and #1205 public-prompt surfaces (independent features) - .claude + src/backend/enterprise submodules: reset to dev's pointers (the branch pinned unrelated/stale commits) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vybe
pushed a commit
that referenced
this pull request
Jul 3, 2026
- migrations.py, test_25_telegram_voice_out.py: parent-squash echoes; PR does not touch them -> take dev's versions (keeps #1337 public_channel_prompt migration and #1397's final test set) - TelegramChannelPanel.vue: dev == feature/25 tip here; keep this branch's intended replacement of the inline voice block with shared VoiceRepliesControl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First slice of epic #24 (outbound voice across channels): Telegram voice-out. Builds the shared TTS foundation that Slack (#26) and WhatsApp (trinity-enterprise#56) reuse unchanged, then wires Telegram as the first consumer.
Shared foundation (epic #24)
services/tts_service.py— one provider-agnostic TTS layer. ElevenLabs synth →ffmpegMP3→OGG/Opus transcode (the chat voice-note codec) → OGG bytes. Shared char cost-cap (TTS_MAX_CHARS, default 1500). Fail-soft everywhere — every path returnsNoneon any problem (no key, over cap, provider/transcode error, ffmpeg missing) so the caller delivers text. Voice is strictly additive.agent_ownership.tts_voice_replies_enabled+tts_voice_idvia a newTtsMixin. Dual-track migration (SQLitedb/migrations.py+ Alembic0011), schema.py + tables.py.GET/PUT /api/agents/{name}/voice-replies(owner-only; enabling requires avoice_id; GET exposesavailable= platform key present).ffmpegadded to the backend image;ELEVENLABS_API_KEY/ELEVENLABS_MODEL_ID/TTS_MAX_CHARSin config +.env.example.Telegram wiring (#25)
telegram_adapter._send_voice(TelegramsendVoice, multipart OGG) + a voice branch insend_response: when enabled with a voice id, speak the reply; fall back to text on any miss.Design decisions (confirmed)
ffmpeg→ OGG/Opus (native voice-note bubble; also what Consistent context usage tracking for stateless executions #56 needs).Test plan
tts_service: cost-cap / no-key / no-voice / empty / provider-error / success; transcode success + ffmpeg-missing + nonzero-rc; end-to-end + short-circuit — 16 tests pass.sendVoice; TTSNone→ text fallback.test_schema_parity,test_migrations, alembic guard); 48 telegram tests pass; app wiring imports clean under conftest.Notes
#56(WhatsApp voice-out) was un-claimed back tostatus-ready— it's blocked on this shared layer and will ride it once merged.#25wasstatus-incubatingwith open design questions; promoted tostatus-in-progressafter the decisions above.Related to abilityai/trinity-enterprise#25