Route OpenClaw chat by HA user

This commit is contained in:
2026-05-06 08:08:18 +02:00
parent 90d525e842
commit 8221098ebc
12 changed files with 285 additions and 68 deletions
@@ -13,7 +13,7 @@
* + subscribes to openclaw_message_received events.
*/
const CARD_VERSION = "0.3.13";
const CARD_VERSION = "0.3.14";
// 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;
@@ -96,6 +96,9 @@ class OpenClawChatCard extends HTMLElement {
this._autoScrollPinned = true;
this._lastHassRenderSignature = null;
this._integrationThinkingTimeout = null;
this._backendHaUserId = null;
this._activeAgentId = null;
this._lastStorageIdentity = null;
}
// ── HA card interface ───────────────────────────────────────────────
@@ -128,8 +131,15 @@ class OpenClawChatCard extends HTMLElement {
set hass(hass) {
const firstSet = !this._hass;
const previousStorageIdentity = this._getStorageIdentity();
this._hass = hass;
const currentSignature = this._getHassRenderSignature(hass);
const currentStorageIdentity = this._getStorageIdentity();
if (currentStorageIdentity !== previousStorageIdentity) {
this._lastStorageIdentity = currentStorageIdentity;
this._resetTransientChatState();
this._restoreMessages();
}
if (firstSet) {
this._subscribeToEvents();
this._syncHistoryFromBackend();
@@ -252,11 +262,19 @@ class OpenClawChatCard extends HTMLElement {
const data = event.data;
if (!data || !data.message) return;
const haUserId = this._getHaUserId();
if (data.ha_user_id && haUserId === "unknown") {
this._syncHistoryFromBackend(1);
return;
}
if (data.ha_user_id && haUserId !== "unknown" && data.ha_user_id !== haUserId) return;
if (data.agent_id && this._activeAgentId && data.agent_id !== this._activeAgentId) return;
// Check if this event is for our session
const sessionId = this._getSessionId();
if (data.session_id && data.session_id !== sessionId) return;
const signature = `${sessionId}|${data.timestamp || ""}|${String(data.message)}`;
const signature = `${haUserId}|${data.agent_id || ""}|${sessionId}|${data.timestamp || ""}|${String(data.message)}`;
if (signature === this._lastAssistantEventSignature) {
return;
}
@@ -295,7 +313,16 @@ class OpenClawChatCard extends HTMLElement {
}
}
_clearChat() {
async _clearChat() {
if (this._hass) {
try {
await this._hass.callService("openclaw", "clear_history", {
session_id: this._getSessionId(),
});
} catch (err) {
console.warn("OpenClaw: backend history clear failed:", err);
}
}
this._messages = [];
this._isProcessing = false;
this._pendingResponses = 0;
@@ -304,11 +331,31 @@ class OpenClawChatCard extends HTMLElement {
this._render();
}
_resetTransientChatState() {
this._isProcessing = false;
this._pendingResponses = 0;
this._clearThinkingTimer();
this._lastAssistantEventSignature = null;
}
// ── Message persistence ──────────────────────────────────────────────
_getHaUserId() {
return (
this._hass?.user?.id ||
this._backendHaUserId ||
"unknown"
);
}
_getStorageIdentity() {
return `${this._getHaUserId()}|${this._getSessionId()}`;
}
_getStorageKey() {
const userId = this._getHaUserId();
const session = this._getSessionId();
return `${STORAGE_PREFIX}${session}`;
return `${STORAGE_PREFIX}${userId}_${session}`;
}
_getSessionId() {
@@ -329,9 +376,8 @@ class OpenClawChatCard extends HTMLElement {
_restoreMessages() {
try {
const stored = sessionStorage.getItem(this._getStorageKey());
if (stored) {
this._messages = JSON.parse(stored);
}
const parsed = stored ? JSON.parse(stored) : [];
this._messages = Array.isArray(parsed) ? parsed : [];
} catch (e) {
// Corrupted data — start fresh
this._messages = [];
@@ -357,6 +403,18 @@ class OpenClawChatCard extends HTMLElement {
});
}
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();
this._restoreMessages();
}
const serverMessages = Array.isArray(result?.messages) ? result.messages : [];
if (!serverMessages.length) return;
@@ -426,6 +484,17 @@ class OpenClawChatCard extends HTMLElement {
typeof result?.thinking_timeout === "number" && result.thinking_timeout >= 10
? result.thinking_timeout * 1000
: null;
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();
this._restoreMessages();
}
if (includePipeline) {
try {
@@ -2185,7 +2254,11 @@ class OpenClawChatCard extends HTMLElement {
const clearBtn = this.shadowRoot.getElementById("clear-btn");
if (clearBtn) {
clearBtn.addEventListener("click", () => {
if (confirm("Clear chat history?")) this._clearChat();
if (confirm("Clear chat history?")) {
this._clearChat().catch((err) => {
console.error("OpenClaw: clear chat failed", err);
});
}
});
}