* fix(tts): config save + Edge provider registration + dark mode chat bubbles
- Wrap TTS config payload in `raw` field for config.patch RPC (#229)
- Always register Edge TTS provider (free, no API key) instead of gating on `enabled` flag
- Fix low-contrast user message bubbles in dark mode chat
* fix(tts): skip duplicate media dispatch when temp file already delivered
When both the agent loop and the message tool dispatch the same TTS
temp file, the first dispatch succeeds and cleanup deletes it. Filter
out missing temp media files before sending to prevent "file not found"
errors and spurious error notifications on Telegram/Slack/Discord.
* feat(tts): include edge-tts in Docker image when Python enabled
Edge TTS is free (no API key) and serves as a universal TTS fallback.
Install it alongside Python in both ENABLE_PYTHON and ENABLE_FULL_SKILLS builds.
* chore(docker): expose build args from .env for compose builds
Pass ENABLE_OTEL, ENABLE_PYTHON, ENABLE_FULL_SKILLS as env-driven
build args so .env can control Docker build features without editing
docker-compose.yml directly.
* fix(tts): hot-reload TTS config on settings change via pub/sub
TTS providers were only registered at startup, so changing provider/API
key via the Web UI had no effect until container restart. Add a
tts-config-reload bus subscriber that rebuilds the TTS manager on
config changes, matching the pattern used by quota, cron, and web_fetch.
Always create a TtsTool at startup (even without providers) so the
reload subscriber can populate it when settings are first configured.
* fix(tts): protect TtsTool.UpdateManager with RWMutex to prevent data race
UpdateManager() can be called from the config reload goroutine while
Execute() reads t.manager concurrently from agent goroutines. Add
sync.RWMutex following the same pattern as WebFetchTool.UpdatePolicy().
Also update setupTTS doc comment which incorrectly stated it could
return nil — Edge TTS is now always registered.
---------
Co-authored-by: viettranx <viettranx@gmail.com>
- Add terminal-state check in executeCreate(): reject blocked_by
referencing completed/cancelled/failed tasks with actionable error
- Add full validation in executeUpdate(): batch query via GetTasksByIDs,
check existence + team membership + terminal state
- Add GetTasksByIDs batch query to TeamStore interface + pg implementation
- Refactor: modularize gateway, skills store, and team tools into
focused files
- Update TEAM.md leader prompt: prefer delegation, plan full task graph
upfront, create tasks in order with blocked_by UUIDs