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:
@@ -2,6 +2,20 @@
|
|||||||
|
|
||||||
All notable changes to the OpenClaw Home Assistant Integration will be documented in this file.
|
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
|
## [0.1.9] - 2026-02-20
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -318,13 +318,16 @@ def _async_register_services(hass: HomeAssistant) -> None:
|
|||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
choices = response.get("choices", [])
|
assistant_message = _extract_assistant_message(response)
|
||||||
if choices:
|
|
||||||
assistant_message = (
|
|
||||||
choices[0].get("message", {}).get("content", "")
|
|
||||||
)
|
|
||||||
model_used = response.get("model", "unknown")
|
model_used = response.get("model", "unknown")
|
||||||
|
|
||||||
|
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()),
|
||||||
|
)
|
||||||
|
|
||||||
hass.bus.async_fire(
|
hass.bus.async_fire(
|
||||||
EVENT_MESSAGE_RECEIVED,
|
EVENT_MESSAGE_RECEIVED,
|
||||||
{
|
{
|
||||||
@@ -338,6 +341,15 @@ def _async_register_services(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
except OpenClawApiError as err:
|
except OpenClawApiError as err:
|
||||||
_LOGGER.error("Failed to send message to OpenClaw: %s", 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:
|
async def handle_clear_history(call: ServiceCall) -> None:
|
||||||
"""Handle the openclaw.clear_history service call."""
|
"""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:
|
if isinstance(entry_data, dict) and "client" in entry_data:
|
||||||
return entry_data
|
return entry_data
|
||||||
return None
|
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
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
|
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"version": "0.1.9",
|
"version": "0.1.11",
|
||||||
"dependencies": ["conversation"],
|
"dependencies": ["conversation"],
|
||||||
"after_dependencies": ["hassio", "lovelace"]
|
"after_dependencies": ["hassio", "lovelace"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -836,16 +836,23 @@ class OpenClawChatCardEditor extends HTMLElement {
|
|||||||
|
|
||||||
// ── Card registration ───────────────────────────────────────────────────────
|
// ── Card registration ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if (!customElements.get("openclaw-chat-card-editor")) {
|
||||||
customElements.define("openclaw-chat-card-editor", OpenClawChatCardEditor);
|
customElements.define("openclaw-chat-card-editor", OpenClawChatCardEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!customElements.get("openclaw-chat-card")) {
|
||||||
customElements.define("openclaw-chat-card", OpenClawChatCard);
|
customElements.define("openclaw-chat-card", OpenClawChatCard);
|
||||||
|
}
|
||||||
|
|
||||||
window.customCards = window.customCards || [];
|
window.customCards = window.customCards || [];
|
||||||
|
if (!window.customCards.some((card) => card?.type === "openclaw-chat-card")) {
|
||||||
window.customCards.push({
|
window.customCards.push({
|
||||||
type: "openclaw-chat-card",
|
type: "openclaw-chat-card",
|
||||||
name: "OpenClaw Chat",
|
name: "OpenClaw Chat",
|
||||||
description: "Chat interface for OpenClaw AI Assistant with streaming, voice, and file support.",
|
description: "Chat interface for OpenClaw AI Assistant with streaming, voice, and file support.",
|
||||||
preview: true,
|
preview: true,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.info(
|
console.info(
|
||||||
`%c OPENCLAW-CHAT-CARD %c v${CARD_VERSION} `,
|
`%c OPENCLAW-CHAT-CARD %c v${CARD_VERSION} `,
|
||||||
|
|||||||
@@ -836,16 +836,23 @@ class OpenClawChatCardEditor extends HTMLElement {
|
|||||||
|
|
||||||
// ── Card registration ───────────────────────────────────────────────────────
|
// ── Card registration ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if (!customElements.get("openclaw-chat-card-editor")) {
|
||||||
customElements.define("openclaw-chat-card-editor", OpenClawChatCardEditor);
|
customElements.define("openclaw-chat-card-editor", OpenClawChatCardEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!customElements.get("openclaw-chat-card")) {
|
||||||
customElements.define("openclaw-chat-card", OpenClawChatCard);
|
customElements.define("openclaw-chat-card", OpenClawChatCard);
|
||||||
|
}
|
||||||
|
|
||||||
window.customCards = window.customCards || [];
|
window.customCards = window.customCards || [];
|
||||||
|
if (!window.customCards.some((card) => card?.type === "openclaw-chat-card")) {
|
||||||
window.customCards.push({
|
window.customCards.push({
|
||||||
type: "openclaw-chat-card",
|
type: "openclaw-chat-card",
|
||||||
name: "OpenClaw Chat",
|
name: "OpenClaw Chat",
|
||||||
description: "Chat interface for OpenClaw AI Assistant with streaming, voice, and file support.",
|
description: "Chat interface for OpenClaw AI Assistant with streaming, voice, and file support.",
|
||||||
preview: true,
|
preview: true,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.info(
|
console.info(
|
||||||
`%c OPENCLAW-CHAT-CARD %c v${CARD_VERSION} `,
|
`%c OPENCLAW-CHAT-CARD %c v${CARD_VERSION} `,
|
||||||
|
|||||||
Reference in New Issue
Block a user