Update changelog for version 0.1.11, enhance assistant message extraction, add fallback event emission on API errors, and improve custom card registration

This commit is contained in:
techartdev
2026-02-20 16:26:59 +02:00
parent 612deda8b9
commit e6fb9e2914
5 changed files with 118 additions and 32 deletions
+14
View File
@@ -2,6 +2,20 @@
All notable changes to the OpenClaw Home Assistant Integration will be documented in this file.
## [0.1.11] - 2026-02-20
### Fixed
- Chat card no longer waits forever when the gateway returns a non-`choices[0].message.content` response shape.
- `openclaw.send_message` now extracts assistant text from multiple OpenAI-compatible formats (`choices`, `output_text`, `response`, `message`, `content`, `answer`).
- Added fallback event emission on API errors so the frontend always receives a response and exits the typing state.
## [0.1.10] - 2026-02-20
### Fixed
- Made custom card registration idempotent (`customElements.get(...)` guards) to prevent duplicate-load exceptions that can block card discovery.
- Prevented duplicate `window.customCards` entries for `openclaw-chat-card`.
- Synced registration hardening in both packaged and root `www/` card scripts.
## [0.1.9] - 2026-02-20
### Fixed
+73 -15
View File
@@ -318,26 +318,38 @@ def _async_register_services(hass: HomeAssistant) -> None:
session_id=session_id,
)
choices = response.get("choices", [])
if choices:
assistant_message = (
choices[0].get("message", {}).get("content", "")
)
model_used = response.get("model", "unknown")
assistant_message = _extract_assistant_message(response)
model_used = response.get("model", "unknown")
hass.bus.async_fire(
EVENT_MESSAGE_RECEIVED,
{
ATTR_MESSAGE: assistant_message,
ATTR_SESSION_ID: session_id,
ATTR_MODEL: model_used,
ATTR_TIMESTAMP: datetime.now(timezone.utc).isoformat(),
},
if not assistant_message:
assistant_message = "Response received, but no readable message content was found."
_LOGGER.warning(
"OpenClaw response had no parseable assistant message. Keys: %s",
list(response.keys()),
)
coordinator.update_last_activity()
hass.bus.async_fire(
EVENT_MESSAGE_RECEIVED,
{
ATTR_MESSAGE: assistant_message,
ATTR_SESSION_ID: session_id,
ATTR_MODEL: model_used,
ATTR_TIMESTAMP: datetime.now(timezone.utc).isoformat(),
},
)
coordinator.update_last_activity()
except OpenClawApiError as err:
_LOGGER.error("Failed to send message to OpenClaw: %s", err)
hass.bus.async_fire(
EVENT_MESSAGE_RECEIVED,
{
ATTR_MESSAGE: f"OpenClaw error: {err}",
ATTR_SESSION_ID: session_id,
ATTR_MODEL: "unknown",
ATTR_TIMESTAMP: datetime.now(timezone.utc).isoformat(),
},
)
async def handle_clear_history(call: ServiceCall) -> None:
"""Handle the openclaw.clear_history service call."""
@@ -366,3 +378,49 @@ def _get_first_entry_data(hass: HomeAssistant) -> dict[str, Any] | None:
if isinstance(entry_data, dict) and "client" in entry_data:
return entry_data
return None
def _coerce_text(value: Any) -> str | None:
"""Convert known message content shapes to plain text."""
if isinstance(value, str):
text = value.strip()
return text or None
if isinstance(value, list):
parts: list[str] = []
for item in value:
if isinstance(item, str):
if item.strip():
parts.append(item.strip())
continue
if isinstance(item, dict):
text_part = item.get("text") or item.get("content")
if isinstance(text_part, str) and text_part.strip():
parts.append(text_part.strip())
if parts:
return "\n".join(parts)
return None
def _extract_assistant_message(response: dict[str, Any]) -> str | None:
"""Extract assistant text from multiple OpenAI-compatible response shapes."""
choices = response.get("choices")
if isinstance(choices, list) and choices:
first = choices[0]
if isinstance(first, dict):
message = first.get("message")
if isinstance(message, dict):
extracted = _coerce_text(message.get("content"))
if extracted:
return extracted
extracted = _coerce_text(first.get("text"))
if extracted:
return extracted
for key in ("output_text", "response", "message", "content", "answer"):
extracted = _coerce_text(response.get(key))
if extracted:
return extracted
return None
+1 -1
View File
@@ -8,7 +8,7 @@
"iot_class": "local_polling",
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
"requirements": [],
"version": "0.1.9",
"version": "0.1.11",
"dependencies": ["conversation"],
"after_dependencies": ["hassio", "lovelace"]
}
@@ -836,16 +836,23 @@ class OpenClawChatCardEditor extends HTMLElement {
// ── Card registration ───────────────────────────────────────────────────────
customElements.define("openclaw-chat-card-editor", OpenClawChatCardEditor);
customElements.define("openclaw-chat-card", OpenClawChatCard);
if (!customElements.get("openclaw-chat-card-editor")) {
customElements.define("openclaw-chat-card-editor", OpenClawChatCardEditor);
}
if (!customElements.get("openclaw-chat-card")) {
customElements.define("openclaw-chat-card", OpenClawChatCard);
}
window.customCards = window.customCards || [];
window.customCards.push({
type: "openclaw-chat-card",
name: "OpenClaw Chat",
description: "Chat interface for OpenClaw AI Assistant with streaming, voice, and file support.",
preview: true,
});
if (!window.customCards.some((card) => card?.type === "openclaw-chat-card")) {
window.customCards.push({
type: "openclaw-chat-card",
name: "OpenClaw Chat",
description: "Chat interface for OpenClaw AI Assistant with streaming, voice, and file support.",
preview: true,
});
}
console.info(
`%c OPENCLAW-CHAT-CARD %c v${CARD_VERSION} `,
+15 -8
View File
@@ -836,16 +836,23 @@ class OpenClawChatCardEditor extends HTMLElement {
// ── Card registration ───────────────────────────────────────────────────────
customElements.define("openclaw-chat-card-editor", OpenClawChatCardEditor);
customElements.define("openclaw-chat-card", OpenClawChatCard);
if (!customElements.get("openclaw-chat-card-editor")) {
customElements.define("openclaw-chat-card-editor", OpenClawChatCardEditor);
}
if (!customElements.get("openclaw-chat-card")) {
customElements.define("openclaw-chat-card", OpenClawChatCard);
}
window.customCards = window.customCards || [];
window.customCards.push({
type: "openclaw-chat-card",
name: "OpenClaw Chat",
description: "Chat interface for OpenClaw AI Assistant with streaming, voice, and file support.",
preview: true,
});
if (!window.customCards.some((card) => card?.type === "openclaw-chat-card")) {
window.customCards.push({
type: "openclaw-chat-card",
name: "OpenClaw Chat",
description: "Chat interface for OpenClaw AI Assistant with streaming, voice, and file support.",
preview: true,
});
}
console.info(
`%c OPENCLAW-CHAT-CARD %c v${CARD_VERSION} `,