Release OpenClaw HA integration 0.1.66
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user