diff --git a/CHANGELOG.md b/CHANGELOG.md index 0871bfe..6e54c38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to the OpenClaw Home Assistant Integration will be documented in this file. +## [0.1.44] - 2026-02-21 + +### Fixed +- Fixed chat-card settings sync to always read latest integration options instead of a potentially stale cached config entry. +- Wake-word disable now applies reliably after unchecking in integration options. +- Browser voice listening status now only shows wake-word requirement when wake word is actually enabled. + ## [0.1.43] - 2026-02-21 ### Fixed diff --git a/custom_components/openclaw/__init__.py b/custom_components/openclaw/__init__.py index a215c1d..907b594 100644 --- a/custom_components/openclaw/__init__.py +++ b/custom_components/openclaw/__init__.py @@ -92,7 +92,7 @@ _CARD_PATH = Path(__file__).parent / "www" / _CARD_FILENAME # URL at which the card JS is served (registered via register_static_path) _CARD_STATIC_URL = f"/openclaw/{_CARD_FILENAME}" # Versioned URL used for Lovelace resource registration to avoid stale browser cache -_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.43" +_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.44" OpenClawConfigEntry = ConfigEntry @@ -151,6 +151,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: OpenClawConfigEntry) -> "coordinator": coordinator, "addon_config_path": addon_config_path, "entry": entry, + "entry_id": entry.entry_id, } # First data fetch — if it fails the coordinator marks entities unavailable @@ -386,8 +387,7 @@ def _async_register_services(hass: HomeAssistant) -> None: client: OpenClawApiClient = entry_data["client"] coordinator: OpenClawCoordinator = entry_data["coordinator"] - entry: ConfigEntry = entry_data["entry"] - options = entry.options + options = _get_entry_options(hass, entry_data) try: include_context = options.get( @@ -573,6 +573,25 @@ def _get_first_entry_data(hass: HomeAssistant) -> dict[str, Any] | None: return None +def _get_entry_options(hass: HomeAssistant, entry_data: dict[str, Any]) -> dict[str, Any]: + """Return latest config entry options for an integration entry data payload.""" + latest_entry: ConfigEntry | None = None + + entry_id = entry_data.get("entry_id") + if isinstance(entry_id, str): + latest_entry = hass.config_entries.async_get_entry(entry_id) + + if latest_entry is None: + cached_entry = entry_data.get("entry") + cached_entry_id = getattr(cached_entry, "entry_id", None) + if isinstance(cached_entry_id, str): + latest_entry = hass.config_entries.async_get_entry(cached_entry_id) or cached_entry + elif isinstance(cached_entry, ConfigEntry): + latest_entry = cached_entry + + return latest_entry.options if latest_entry else {} + + def _extract_text_recursive(value: Any, depth: int = 0) -> str | None: """Recursively extract assistant text from nested response payloads.""" if depth > 8: @@ -793,7 +812,7 @@ def _async_register_websocket_api(hass: HomeAssistant) -> None: ) -> None: """Return frontend-related integration settings.""" entry_data = _get_first_entry_data(hass) - options = entry_data.get("entry").options if entry_data and entry_data.get("entry") else {} + options = _get_entry_options(hass, entry_data) if entry_data else {} connection.send_result( msg["id"], { diff --git a/custom_components/openclaw/manifest.json b/custom_components/openclaw/manifest.json index 1119b4b..95a363f 100644 --- a/custom_components/openclaw/manifest.json +++ b/custom_components/openclaw/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues", "requirements": [], - "version": "0.1.43", + "version": "0.1.44", "dependencies": ["conversation"], "after_dependencies": ["hassio", "lovelace"] } diff --git a/custom_components/openclaw/www/openclaw-chat-card.js b/custom_components/openclaw/www/openclaw-chat-card.js index eab0ff9..b765c88 100644 --- a/custom_components/openclaw/www/openclaw-chat-card.js +++ b/custom_components/openclaw/www/openclaw-chat-card.js @@ -350,7 +350,7 @@ class OpenClawChatCard extends HTMLElement { }); } - this._wakeWordEnabled = !!result?.wake_word_enabled; + this._wakeWordEnabled = this._coerceBoolean(result?.wake_word_enabled, false); this._wakeWord = (result?.wake_word || "hey openclaw").toString().trim().toLowerCase(); this._allowBraveWebSpeechIntegration = !!result?.allow_brave_webspeech; this._voiceProviderIntegration = @@ -543,6 +543,17 @@ class OpenClawChatCard extends HTMLElement { return languageMap[cleaned] || `${cleaned}-${cleaned.toUpperCase()}`; } + _coerceBoolean(value, fallback = false) { + if (typeof value === "boolean") return value; + if (typeof value === "number") return value !== 0; + if (typeof value === "string") { + const normalized = value.trim().toLowerCase(); + if (["true", "1", "yes", "on"].includes(normalized)) return true; + if (["false", "0", "no", "off", ""].includes(normalized)) return false; + } + return fallback; + } + _getSpeechRecognitionLanguage() { if (this._speechLangOverride) { return this._speechLangOverride; @@ -1024,7 +1035,9 @@ class OpenClawChatCard extends HTMLElement { this._recognition.continuous = this._isVoiceMode; this._recognition.lang = this._getSpeechRecognitionLanguage(); this._voiceStatus = this._isVoiceMode - ? `Listening (${this._recognition.lang}, wake word: ${this._wakeWord || "hey openclaw"})` + ? this._wakeWordEnabled + ? `Listening (${this._recognition.lang}, wake word: ${this._wakeWord || "hey openclaw"})` + : `Listening (${this._recognition.lang})` : `Listening (${this._recognition.lang})…`; this._recognition.onresult = (event) => { diff --git a/www/openclaw-chat-card.js b/www/openclaw-chat-card.js index 3b26fe1..b531892 100644 --- a/www/openclaw-chat-card.js +++ b/www/openclaw-chat-card.js @@ -1,7 +1,7 @@ (async () => { try { if (!customElements.get("openclaw-chat-card")) { - const src = "/openclaw/openclaw-chat-card.js?v=0.1.43"; + const src = "/openclaw/openclaw-chat-card.js?v=0.1.44"; console.info("OpenClaw loader importing", src); await import(src); }