From bff925a67901a70d9a8317cfd1842273844751ff Mon Sep 17 00:00:00 2001 From: Jon Saad-Falcon <41205309+jonsaadfalcon@users.noreply.github.com> Date: Thu, 2 Apr 2026 14:25:55 -0700 Subject: [PATCH] fix: wire DigestConfig through SDK and system for end-to-end digest - Inject DigestConfig (persona, sections, TTS backend, voice_id) into MorningDigestAgent from both JarvisSystem._run_agent and Jarvis SDK - Always inject digest_collect + text_to_speech tools for digest agent - Fix TTS tool: import speech backends before checking TTSRegistry - Fix Cartesia: default to British Butler voice when voice_id is empty - Fix DigestStore: allow cross-thread SQLite access for FastAPI - Propagate agent metadata (audio_path) through system return dict - Add music section to default source map in MorningDigestAgent Tested end-to-end: Qwen3.5 9B (Ollama) + Cartesia TTS + real data from Oura, Gmail, Calendar, Tasks, Spotify, Apple Music. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/openjarvis/agents/morning_digest.py | 3 ++- src/openjarvis/sdk.py | 24 ++++++++++++++++++++++++ src/openjarvis/speech/cartesia_tts.py | 4 ++++ src/openjarvis/system.py | 25 +++++++++++++++++++++++++ src/openjarvis/tools/text_to_speech.py | 3 +++ 5 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/openjarvis/agents/morning_digest.py b/src/openjarvis/agents/morning_digest.py index 876f63ef..5583ee97 100644 --- a/src/openjarvis/agents/morning_digest.py +++ b/src/openjarvis/agents/morning_digest.py @@ -69,7 +69,8 @@ class MorningDigestAgent(ToolUsingAgent): default_source_map = { "messages": ["gmail", "slack", "google_tasks"], "calendar": ["gcalendar"], - "health": ["oura"], + "health": ["oura", "apple_health"], + "music": ["spotify", "apple_music"], "world": [], # Handled by web_search, not connectors } sources = set() diff --git a/src/openjarvis/sdk.py b/src/openjarvis/sdk.py index 835f9e29..10fd2c69 100644 --- a/src/openjarvis/sdk.py +++ b/src/openjarvis/sdk.py @@ -482,6 +482,30 @@ class Jarvis: if self._capability_policy is not None: agent_kwargs["capability_policy"] = self._capability_policy + # Inject DigestConfig for morning_digest agent + if agent_name == "morning_digest" and hasattr(self._config, "digest"): + dc = self._config.digest + section_sources: Dict[str, Any] = {} + for s in dc.sections: + sc = getattr(dc, s, None) + if sc and hasattr(sc, "sources"): + section_sources[s] = sc.sources + agent_kwargs.update({ + "persona": dc.persona, + "sections": dc.sections, + "section_sources": section_sources, + "timezone": dc.timezone, + "voice_id": dc.voice_id, + "tts_backend": dc.tts_backend, + }) + # Ensure digest agent always has its required tools + from openjarvis.tools.digest_collect import DigestCollectTool + from openjarvis.tools.text_to_speech import TextToSpeechTool + + digest_tools = [DigestCollectTool(), TextToSpeechTool()] + existing = agent_kwargs.get("tools", []) + agent_kwargs["tools"] = digest_tools + list(existing) + agent_obj = agent_cls(self._engine, model_name, **agent_kwargs) ctx = AgentContext() diff --git a/src/openjarvis/speech/cartesia_tts.py b/src/openjarvis/speech/cartesia_tts.py index 21a15a58..256a0c10 100644 --- a/src/openjarvis/speech/cartesia_tts.py +++ b/src/openjarvis/speech/cartesia_tts.py @@ -70,6 +70,10 @@ class CartesiaTTSBackend(TTSBackend): if not self._api_key: raise RuntimeError("CARTESIA_API_KEY not set") + # Default to "British Butler" voice — warm, authoritative, Jarvis-like + if not voice_id: + voice_id = "a0e99841-438c-4a64-b679-ae501e7d6091" + audio = _cartesia_synthesize( self._api_key, text, diff --git a/src/openjarvis/system.py b/src/openjarvis/system.py index e04cfbbb..09ae0303 100644 --- a/src/openjarvis/system.py +++ b/src/openjarvis/system.py @@ -211,6 +211,30 @@ class JarvisSystem: agent_kwargs["session_store"] = self.session_store agent_kwargs["memory_backend"] = self.memory_backend + # Inject DigestConfig when instantiating the morning_digest agent + if agent_name == "morning_digest" and hasattr(self.config, "digest"): + dc = self.config.digest + section_sources = {} + for s in dc.sections: + sc = getattr(dc, s, None) + if sc and hasattr(sc, "sources"): + section_sources[s] = sc.sources + agent_kwargs.update({ + "persona": dc.persona, + "sections": dc.sections, + "section_sources": section_sources, + "timezone": dc.timezone, + "voice_id": dc.voice_id, + "tts_backend": dc.tts_backend, + }) + # Ensure digest agent always has its required tools + from openjarvis.tools.digest_collect import DigestCollectTool + from openjarvis.tools.text_to_speech import TextToSpeechTool + + digest_tools = [DigestCollectTool(), TextToSpeechTool()] + existing = agent_kwargs.get("tools", []) + agent_kwargs["tools"] = digest_tools + list(existing) + try: ag = agent_cls(self.engine, self.model, **agent_kwargs) except TypeError: @@ -304,6 +328,7 @@ class JarvisSystem: for tr in getattr(result, "tool_results", []) ], "turns": getattr(result, "turns", 1), + "metadata": getattr(result, "metadata", {}), "model": self.model, "engine": self.engine_key, "_telemetry": _telemetry, diff --git a/src/openjarvis/tools/text_to_speech.py b/src/openjarvis/tools/text_to_speech.py index d17ebd82..a0378bf9 100644 --- a/src/openjarvis/tools/text_to_speech.py +++ b/src/openjarvis/tools/text_to_speech.py @@ -53,6 +53,9 @@ class TextToSpeechTool(BaseTool): ) def execute(self, **params: Any) -> ToolResult: + # Ensure TTS backends are registered + import openjarvis.speech # noqa: F401 + text = params.get("text", "") voice_id = params.get("voice_id", "") backend_key = params.get("backend", "cartesia")