Release OpenClaw HA integration 0.1.66

This commit is contained in:
2026-05-06 13:08:57 +02:00
parent 8221098ebc
commit 2a02c18b86
6 changed files with 301 additions and 136 deletions
@@ -9,11 +9,11 @@
* - Voice input (WebSpeech / MediaRecorder)
* - Voice mode toggle (continuous conversation)
*
* Communication: uses HA WebSocket API → openclaw.send_message service
* + subscribes to openclaw_message_received events.
* Communication: uses HA WebSocket API → openclaw/send_message and receives
* the assistant reply directly on the same request.
*/
const CARD_VERSION = "0.3.14";
const CARD_VERSION = "0.3.17";
// Max time (ms) to show the thinking indicator before falling back to an error (default; overridable via card config `thinking_timeout` in seconds)
const THINKING_TIMEOUT_MS = 120_000;
@@ -239,16 +239,9 @@ class OpenClawChatCard extends HTMLElement {
// ── Event subscription ──────────────────────────────────────────────
async _subscribeToEvents() {
if (!this._hass || this._eventUnsubscribe) return;
try {
this._eventUnsubscribe = await this._hass.connection.subscribeEvents(
(event) => this._handleOpenClawEvent(event),
"openclaw_message_received"
);
} catch (err) {
console.error("OpenClaw: Failed to subscribe to events:", err);
}
// Non-admin HA users cannot subscribe to arbitrary custom bus events.
// Chat replies are returned through the integration websocket command.
return;
}
_unsubscribeEvents() {
@@ -416,12 +409,18 @@ class OpenClawChatCard extends HTMLElement {
}
const serverMessages = Array.isArray(result?.messages) ? result.messages : [];
if (!serverMessages.length) return;
if (!serverMessages.length) {
this._retryHistorySyncIfProcessing(retries);
return;
}
const validMessages = serverMessages.filter(
(m) => m && (m.role === "user" || m.role === "assistant") && typeof m.content === "string"
);
if (!validMessages.length) return;
if (!validMessages.length) {
this._retryHistorySyncIfProcessing(retries);
return;
}
const shouldReplace =
validMessages.length > this._messages.length ||
@@ -441,20 +440,30 @@ class OpenClawChatCard extends HTMLElement {
this._persistMessages();
this._render();
this._scrollToBottom();
} else {
this._retryHistorySyncIfProcessing(retries);
}
} catch (err) {
console.debug("OpenClaw: history sync skipped:", err);
if (retries > 0) {
if (this._historySyncRetryTimer) {
clearTimeout(this._historySyncRetryTimer);
}
this._historySyncRetryTimer = setTimeout(() => {
this._syncHistoryFromBackend(retries - 1);
}, 1500);
}
this._scheduleHistorySyncRetry(retries);
}
}
_retryHistorySyncIfProcessing(retries = 0) {
if (!this._isProcessing && this._pendingResponses <= 0) return;
this._scheduleHistorySyncRetry(retries);
}
_scheduleHistorySyncRetry(retries = 0) {
if (retries <= 0) return;
if (this._historySyncRetryTimer) {
clearTimeout(this._historySyncRetryTimer);
}
this._historySyncRetryTimer = setTimeout(() => {
this._syncHistoryFromBackend(retries - 1);
}, 1500);
}
async _loadIntegrationSettings(includePipeline = true) {
if (!this._hass) return;
@@ -588,8 +597,6 @@ class OpenClawChatCard extends HTMLElement {
async _sendMessage(text, source = null) {
if (!text || !text.trim() || !this._hass) return;
await this._subscribeToEvents();
const message = text.trim();
this._addMessage("user", message);
@@ -608,15 +615,56 @@ class OpenClawChatCard extends HTMLElement {
this._scrollToBottom();
try {
await this._hass.callService("openclaw", "send_message", {
let result;
const payload = {
type: "openclaw/send_message",
message: message,
source: source || undefined,
session_id: this._config.session_id || undefined,
});
};
if (typeof this._hass.callWS === "function") {
result = await this._hass.callWS(payload);
} else {
result = await this._hass.connection.sendMessagePromise(payload);
}
setTimeout(() => {
this._syncHistoryFromBackend(1);
}, 1200);
const previousStorageIdentity = this._getStorageIdentity();
if (result?.ha_user_id) {
this._backendHaUserId = result.ha_user_id;
}
if (result?.agent_id) {
this._activeAgentId = result.agent_id;
}
if (this._getStorageIdentity() !== previousStorageIdentity) {
this._resetTransientChatState();
}
const serverMessages = Array.isArray(result?.messages) ? result.messages : [];
const validMessages = serverMessages.filter(
(m) => m && (m.role === "user" || m.role === "assistant") && typeof m.content === "string"
);
if (validMessages.length) {
this._messages = validMessages;
} else {
const thinkingIdx = this._messages.findIndex((m) => m._thinking);
const assistantText = result?.message || "Response received, but no readable message content was found.";
if (thinkingIdx >= 0) {
this._messages[thinkingIdx] = {
role: "assistant",
content: assistantText,
timestamp: result?.timestamp || new Date().toISOString(),
};
} else {
this._addMessage("assistant", assistantText);
}
}
this._isProcessing = false;
this._pendingResponses = 0;
this._clearThinkingTimer();
this._persistMessages();
this._render();
this._scrollToBottom();
} catch (err) {
console.error("OpenClaw: Failed to send message:", err);
// Replace thinking with error