feat: OpenClaw HTTP bridge, HA conversation agent fixes, voice pipeline tooling

- Add openclaw-http-bridge.py: HTTP server translating POST requests to OpenClaw CLI calls
- Add launchd plist for HTTP bridge (port 8081, auto-start)
- Add install-to-docker-ha.sh: deploy custom component to Docker HA via SSH
- Add package-for-ha.sh: create distributable tarball of custom component
- Add test-services.sh: comprehensive voice pipeline service checker

Fixes from code review:
- Use OpenClawAgent (HTTP) in async_setup_entry instead of OpenClawCLIAgent
  (CLI agent fails inside Docker HA where openclaw binary doesn't exist)
- Update all port references from 8080 to 8081 (HTTP bridge port)
- Remove overly permissive CORS headers from HTTP bridge
- Fix zombie process leak: kill child process on CLI timeout
- Remove unused subprocess import in conversation.py
- Add version field to Kokoro TTS Wyoming info
- Update TODO.md with voice pipeline progress
This commit is contained in:
Aodhan Collins
2026-03-08 22:46:04 +00:00
parent 6a0bae2a0b
commit 664bb6d275
16 changed files with 1901 additions and 15 deletions

View File

@@ -26,7 +26,7 @@ A custom conversation agent for Home Assistant that routes all voice/text querie
4. Search for "OpenClaw Conversation"
5. Configure the settings:
- **OpenClaw Host**: `localhost` (or IP of Mac Mini)
- **OpenClaw Port**: `8080`
- **OpenClaw Port**: `8081` (HTTP Bridge)
- **Agent Name**: `main` (or your configured agent)
- **Timeout**: `30` seconds
@@ -49,7 +49,7 @@ Add to your `configuration.yaml`:
```yaml
openclaw_conversation:
openclaw_host: localhost
openclaw_port: 8080
openclaw_port: 8081
agent_name: main
timeout: 30
```
@@ -95,7 +95,7 @@ Once configured, the OpenClaw agent will be available as a conversation agent in
1. Verify OpenClaw host/port settings
2. Ensure OpenClaw is accessible from HA container/host
3. Check network connectivity: `curl http://localhost:8080/status`
3. Check network connectivity: `curl http://localhost:8081/status`
## Files

View File

@@ -22,7 +22,7 @@ from .const import (
DEFAULT_TIMEOUT,
DOMAIN,
)
from .conversation import OpenClawCLIAgent
from .conversation import OpenClawAgent, OpenClawCLIAgent
_LOGGER = logging.getLogger(__name__)
@@ -76,11 +76,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Store entry data
hass.data[DOMAIN][entry.entry_id] = entry.data
# Register the conversation agent
agent = OpenClawCLIAgent(hass, entry.data)
# Register the conversation agent (HTTP-based for cross-network access)
agent = OpenClawAgent(hass, entry.data)
from homeassistant.components import conversation
conversation.async_set_agent(hass, DOMAIN, agent)
conversation.async_set_agent(hass, entry, agent)
_LOGGER.info("OpenClaw Conversation agent registered from config entry")
@@ -91,7 +91,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
# Unregister the conversation agent
from homeassistant.components import conversation
conversation.async_unset_agent(hass, DOMAIN)
conversation.async_unset_agent(hass, entry)
hass.data[DOMAIN].pop(entry.entry_id, None)

View File

@@ -10,7 +10,7 @@ CONF_TIMEOUT = "timeout"
# Defaults
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 8080
DEFAULT_PORT = 8081 # OpenClaw HTTP Bridge (not 8080 gateway)
DEFAULT_AGENT = "main"
DEFAULT_TIMEOUT = 30

View File

@@ -187,8 +187,6 @@ class OpenClawCLIAgent(AbstractConversationAgent):
async def _call_openclaw_cli(self, message: str) -> str:
"""Call OpenClaw CLI and return the response."""
import subprocess
cmd = [
"openclaw",
"agent",
@@ -196,6 +194,7 @@ class OpenClawCLIAgent(AbstractConversationAgent):
"--agent", self.agent_name,
]
proc = None
try:
proc = await asyncio.create_subprocess_exec(
*cmd,
@@ -215,6 +214,9 @@ class OpenClawCLIAgent(AbstractConversationAgent):
return stdout.decode().strip()
except asyncio.TimeoutError:
if proc is not None:
proc.kill()
await proc.wait()
_LOGGER.error("Timeout calling OpenClaw CLI")
return "I'm sorry, the request timed out."
except FileNotFoundError: