Fix thread-unsafe state writes; use conversation-agent dropdown

- entity.py: decorate the dispatcher target _handle_update with @callback so it
  runs on the event loop. Without it, async_dispatcher_send offloaded the plain
  sync callback to an executor thread, and async_write_ha_state raised
  "calls async_write_ha_state from a thread other than the event loop".
- config_flow.py: replace the free-text Assist agent field with
  ConversationAgentSelector (a proper agent dropdown), and use LanguageSelector
  for the language field.
- README: clarify that the built-in Assist agent only handles device commands;
  an LLM conversation agent is required for free-form chat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude
2026-07-22 19:13:08 +03:00
parent f02d48777b
commit 66b5d4a64a
4 changed files with 20 additions and 9 deletions

View File

@@ -95,8 +95,12 @@ will answer.
### Options (⚙ → Configure) ### Options (⚙ → Configure)
- **Route incoming messages to Assist** turn the chat bridge on/off. - **Route incoming messages to Assist** turn the chat bridge on/off.
- **Assist agent / language** pick a specific conversation agent, or leave - **Assist agent / language** pick the conversation agent from the dropdown.
blank for the default pipeline. > **Want free-form chat?** The default *Home Assistant* agent only recognises
> device-control commands and will answer anything else with "Sorry, I
> couldn't understand that". To actually chat, install an LLM conversation
> integration (OpenAI, Google Generative AI, Anthropic, Ollama, …), then
> select that agent here.
- **Reply to any sender** / **Allowed sender addresses** restrict who Assist - **Reply to any sender** / **Allowed sender addresses** restrict who Assist
will answer. will answer.
- **Default recipient** address used by the `notify.reticulum` entity. - **Default recipient** address used by the `notify.reticulum` entity.

View File

@@ -17,6 +17,10 @@ from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.selector import ( from homeassistant.helpers.selector import (
BooleanSelector, BooleanSelector,
ConversationAgentSelector,
ConversationAgentSelectorConfig,
LanguageSelector,
LanguageSelectorConfig,
NumberSelector, NumberSelector,
NumberSelectorConfig, NumberSelectorConfig,
NumberSelectorMode, NumberSelectorMode,
@@ -170,16 +174,14 @@ class ReticulumOptionsFlow(OptionsFlow):
): BooleanSelector(), ): BooleanSelector(),
vol.Optional( vol.Optional(
CONF_ASSIST_AGENT, CONF_ASSIST_AGENT,
description={ description={"suggested_value": opts.get(CONF_ASSIST_AGENT)},
"suggested_value": opts.get(CONF_ASSIST_AGENT, "") ): ConversationAgentSelector(ConversationAgentSelectorConfig()),
},
): TextSelector(),
vol.Optional( vol.Optional(
CONF_ASSIST_LANGUAGE, CONF_ASSIST_LANGUAGE,
description={ description={
"suggested_value": opts.get(CONF_ASSIST_LANGUAGE, "") "suggested_value": opts.get(CONF_ASSIST_LANGUAGE)
}, },
): TextSelector(), ): LanguageSelector(LanguageSelectorConfig()),
vol.Required( vol.Required(
CONF_ALLOW_ALL, default=opts.get(CONF_ALLOW_ALL, True) CONF_ALLOW_ALL, default=opts.get(CONF_ALLOW_ALL, True)
): BooleanSelector(), ): BooleanSelector(),

View File

@@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.entity import DeviceInfo, Entity
@@ -35,5 +36,9 @@ class ReticulumEntity(Entity):
) )
) )
@callback
def _handle_update(self) -> None: def _handle_update(self) -> None:
# Decorated with @callback so the dispatcher runs it on the event loop
# (a plain sync callback would be offloaded to an executor thread, and
# async_write_ha_state is not thread-safe).
self.async_write_ha_state() self.async_write_ha_state()

View File

@@ -12,5 +12,5 @@
"loggers": ["RNS", "LXMF"], "loggers": ["RNS", "LXMF"],
"requirements": ["rns>=0.9.0", "lxmf>=0.6.0"], "requirements": ["rns>=0.9.0", "lxmf>=0.6.0"],
"single_config_entry": true, "single_config_entry": true,
"version": "1.0.3" "version": "1.0.4"
} }