ha-mcp-dev 6.3.1.dev137__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ha_mcp/__init__.py +51 -0
- ha_mcp/__main__.py +753 -0
- ha_mcp/_pypi_marker +0 -0
- ha_mcp/auth/__init__.py +10 -0
- ha_mcp/auth/consent_form.py +501 -0
- ha_mcp/auth/provider.py +786 -0
- ha_mcp/client/__init__.py +10 -0
- ha_mcp/client/rest_client.py +924 -0
- ha_mcp/client/websocket_client.py +656 -0
- ha_mcp/client/websocket_listener.py +340 -0
- ha_mcp/config.py +175 -0
- ha_mcp/errors.py +399 -0
- ha_mcp/py.typed +0 -0
- ha_mcp/resources/card_types.json +48 -0
- ha_mcp/resources/dashboard_guide.md +573 -0
- ha_mcp/server.py +189 -0
- ha_mcp/smoke_test.py +108 -0
- ha_mcp/tools/__init__.py +11 -0
- ha_mcp/tools/backup.py +457 -0
- ha_mcp/tools/device_control.py +687 -0
- ha_mcp/tools/enhanced.py +191 -0
- ha_mcp/tools/helpers.py +184 -0
- ha_mcp/tools/registry.py +199 -0
- ha_mcp/tools/smart_search.py +797 -0
- ha_mcp/tools/tools_addons.py +343 -0
- ha_mcp/tools/tools_areas.py +478 -0
- ha_mcp/tools/tools_blueprints.py +279 -0
- ha_mcp/tools/tools_bug_report.py +570 -0
- ha_mcp/tools/tools_calendar.py +391 -0
- ha_mcp/tools/tools_camera.py +149 -0
- ha_mcp/tools/tools_config_automations.py +508 -0
- ha_mcp/tools/tools_config_dashboards.py +1502 -0
- ha_mcp/tools/tools_config_entry_flow.py +223 -0
- ha_mcp/tools/tools_config_helpers.py +925 -0
- ha_mcp/tools/tools_config_info.py +258 -0
- ha_mcp/tools/tools_config_scripts.py +267 -0
- ha_mcp/tools/tools_entities.py +76 -0
- ha_mcp/tools/tools_filesystem.py +589 -0
- ha_mcp/tools/tools_groups.py +306 -0
- ha_mcp/tools/tools_hacs.py +787 -0
- ha_mcp/tools/tools_history.py +714 -0
- ha_mcp/tools/tools_integrations.py +297 -0
- ha_mcp/tools/tools_labels.py +713 -0
- ha_mcp/tools/tools_mcp_component.py +305 -0
- ha_mcp/tools/tools_registry.py +1115 -0
- ha_mcp/tools/tools_resources.py +622 -0
- ha_mcp/tools/tools_search.py +573 -0
- ha_mcp/tools/tools_service.py +211 -0
- ha_mcp/tools/tools_services.py +352 -0
- ha_mcp/tools/tools_system.py +363 -0
- ha_mcp/tools/tools_todo.py +530 -0
- ha_mcp/tools/tools_traces.py +496 -0
- ha_mcp/tools/tools_updates.py +476 -0
- ha_mcp/tools/tools_utility.py +519 -0
- ha_mcp/tools/tools_voice_assistant.py +345 -0
- ha_mcp/tools/tools_zones.py +387 -0
- ha_mcp/tools/util_helpers.py +199 -0
- ha_mcp/utils/__init__.py +30 -0
- ha_mcp/utils/domain_handlers.py +380 -0
- ha_mcp/utils/fuzzy_search.py +339 -0
- ha_mcp/utils/operation_manager.py +442 -0
- ha_mcp/utils/usage_logger.py +290 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/METADATA +229 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/RECORD +71 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/WHEEL +5 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/entry_points.txt +7 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/licenses/LICENSE +21 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/test_constants.py +15 -0
- tests/test_env_manager.py +336 -0
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bug report tool for Home Assistant MCP Server.
|
|
3
|
+
|
|
4
|
+
This module provides a tool to collect diagnostic information and guide users
|
|
5
|
+
on how to create effective bug reports.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import platform
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Annotated, Any
|
|
14
|
+
|
|
15
|
+
from pydantic import Field
|
|
16
|
+
|
|
17
|
+
from ha_mcp import __version__
|
|
18
|
+
|
|
19
|
+
from ..utils.usage_logger import AVG_LOG_ENTRIES_PER_TOOL, get_recent_logs, get_startup_logs
|
|
20
|
+
from .helpers import log_tool_usage
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _detect_installation_method() -> str:
|
|
26
|
+
"""
|
|
27
|
+
Detect how ha-mcp was installed.
|
|
28
|
+
|
|
29
|
+
Returns one of: pyinstaller, addon, docker, git, pypi, unknown
|
|
30
|
+
"""
|
|
31
|
+
# 1. PyInstaller binary
|
|
32
|
+
if getattr(sys, "frozen", False):
|
|
33
|
+
return "pyinstaller"
|
|
34
|
+
|
|
35
|
+
# 2. Home Assistant Add-on (has supervisor token)
|
|
36
|
+
if os.environ.get("SUPERVISOR_TOKEN"):
|
|
37
|
+
return "addon"
|
|
38
|
+
|
|
39
|
+
# 3. Docker container (non-addon)
|
|
40
|
+
if Path("/.dockerenv").exists():
|
|
41
|
+
return "docker"
|
|
42
|
+
|
|
43
|
+
# 4. Git clone - check for .git directory relative to package
|
|
44
|
+
try:
|
|
45
|
+
# Go up from tools_bug_report.py -> tools -> ha_mcp -> src -> project_root
|
|
46
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
47
|
+
if (project_root / ".git").exists():
|
|
48
|
+
return "git"
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
# 5. PyPI install - marker file exists in package
|
|
53
|
+
try:
|
|
54
|
+
marker_path = Path(__file__).parent.parent / "_pypi_marker"
|
|
55
|
+
if marker_path.exists():
|
|
56
|
+
return "pypi"
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
# 6. Default - unknown
|
|
61
|
+
return "unknown"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _detect_platform() -> dict[str, str]:
|
|
65
|
+
"""Detect platform information."""
|
|
66
|
+
return {
|
|
67
|
+
"os": platform.system(), # Windows, Darwin, Linux
|
|
68
|
+
"os_release": platform.release(),
|
|
69
|
+
"os_version": platform.version(),
|
|
70
|
+
"architecture": platform.machine(),
|
|
71
|
+
"python_version": platform.python_version(),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
76
|
+
"""Register bug report tools with the MCP server."""
|
|
77
|
+
|
|
78
|
+
@mcp.tool(
|
|
79
|
+
annotations={
|
|
80
|
+
"idempotentHint": True,
|
|
81
|
+
"readOnlyHint": True,
|
|
82
|
+
"tags": ["system", "diagnostics", "feedback"],
|
|
83
|
+
"title": "Report Issue or Feedback",
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
@log_tool_usage
|
|
87
|
+
async def ha_report_issue(
|
|
88
|
+
tool_call_count: Annotated[
|
|
89
|
+
int,
|
|
90
|
+
Field(
|
|
91
|
+
default=10,
|
|
92
|
+
ge=1,
|
|
93
|
+
le=16,
|
|
94
|
+
description=(
|
|
95
|
+
"Number of tool calls made since the issue started. "
|
|
96
|
+
"This determines how many log entries to include. "
|
|
97
|
+
"Count how many ha_* tools were called from when the issue began. "
|
|
98
|
+
"Default: 10. Max: 16 (limited by 200-entry log buffer: 16*4*3=192)"
|
|
99
|
+
),
|
|
100
|
+
),
|
|
101
|
+
] = 10,
|
|
102
|
+
) -> dict[str, Any]:
|
|
103
|
+
"""
|
|
104
|
+
Collect diagnostic information for filing issue reports or feedback.
|
|
105
|
+
|
|
106
|
+
This tool generates templates for TWO types of reports:
|
|
107
|
+
1. **Runtime Bug Report** - For ha-mcp errors, failures, unexpected behavior
|
|
108
|
+
2. **Agent Behavior Feedback** - For AI agent inefficiency, wrong tool usage
|
|
109
|
+
|
|
110
|
+
**IMPORTANT FOR AI AGENTS:**
|
|
111
|
+
You MUST analyze the conversation context to determine which template to present:
|
|
112
|
+
|
|
113
|
+
🐛 **Present RUNTIME BUG template if:**
|
|
114
|
+
- User reports an error, failure, or unexpected behavior
|
|
115
|
+
- A tool returned an error or incorrect result
|
|
116
|
+
- Something is broken or not working in ha-mcp
|
|
117
|
+
|
|
118
|
+
🤖 **Present AGENT BEHAVIOR template if:**
|
|
119
|
+
- User mentions YOU (the agent) used the wrong tool
|
|
120
|
+
- User suggests a more efficient workflow
|
|
121
|
+
- User reports YOUR inefficiency or mistakes
|
|
122
|
+
- User says you should have done something differently
|
|
123
|
+
|
|
124
|
+
**If unclear which type, ASK the user:**
|
|
125
|
+
"Are you reporting a bug in ha-mcp, or providing feedback on how I used the tools?"
|
|
126
|
+
|
|
127
|
+
**WHEN TO USE THIS TOOL:**
|
|
128
|
+
- "I want to file a bug/issue/report"
|
|
129
|
+
- "This isn't working"
|
|
130
|
+
- "You should have used [other tool]"
|
|
131
|
+
- "That was inefficient"
|
|
132
|
+
|
|
133
|
+
**OUTPUT:**
|
|
134
|
+
Returns both templates. Choose the appropriate one based on context.
|
|
135
|
+
"""
|
|
136
|
+
# Detect installation method and platform
|
|
137
|
+
install_method = _detect_installation_method()
|
|
138
|
+
platform_info = _detect_platform()
|
|
139
|
+
|
|
140
|
+
diagnostic_info: dict[str, Any] = {
|
|
141
|
+
"ha_mcp_version": __version__,
|
|
142
|
+
"installation_method": install_method,
|
|
143
|
+
"platform": platform_info,
|
|
144
|
+
"connection_status": "Unknown",
|
|
145
|
+
"home_assistant_version": "Unknown",
|
|
146
|
+
"entity_count": 0,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# Try to get Home Assistant config and connection status
|
|
150
|
+
try:
|
|
151
|
+
config = await client.get_config()
|
|
152
|
+
diagnostic_info["connection_status"] = "Connected"
|
|
153
|
+
diagnostic_info["home_assistant_version"] = config.get(
|
|
154
|
+
"version", "Unknown"
|
|
155
|
+
)
|
|
156
|
+
diagnostic_info["location_name"] = config.get("location_name", "Unknown")
|
|
157
|
+
diagnostic_info["time_zone"] = config.get("time_zone", "Unknown")
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.warning(f"Failed to get Home Assistant config: {e}")
|
|
160
|
+
diagnostic_info["connection_status"] = f"Connection Error: {str(e)}"
|
|
161
|
+
|
|
162
|
+
# Try to get entity count
|
|
163
|
+
try:
|
|
164
|
+
states = await client.get_states()
|
|
165
|
+
if states:
|
|
166
|
+
diagnostic_info["entity_count"] = len(states)
|
|
167
|
+
except Exception as e:
|
|
168
|
+
logger.warning(f"Failed to get entity count: {e}")
|
|
169
|
+
|
|
170
|
+
# Calculate how many log entries to retrieve
|
|
171
|
+
# Formula: AVG_LOG_ENTRIES_PER_TOOL * 4 * tool_call_count (doubled from 2x to 4x)
|
|
172
|
+
max_log_entries = AVG_LOG_ENTRIES_PER_TOOL * 4 * tool_call_count
|
|
173
|
+
recent_logs = get_recent_logs(max_entries=max_log_entries)
|
|
174
|
+
|
|
175
|
+
# Get startup logs (first minute of server operation)
|
|
176
|
+
startup_logs = get_startup_logs()
|
|
177
|
+
|
|
178
|
+
# Format logs for inclusion (sanitized summary)
|
|
179
|
+
log_summary = _format_logs_for_report(recent_logs)
|
|
180
|
+
startup_log_summary = _format_startup_logs(startup_logs)
|
|
181
|
+
|
|
182
|
+
# Build the formatted report
|
|
183
|
+
report_lines = [
|
|
184
|
+
"=== ha-mcp Bug Report Info ===",
|
|
185
|
+
"",
|
|
186
|
+
f"ha-mcp Version: {diagnostic_info['ha_mcp_version']}",
|
|
187
|
+
f"Installation Method: {diagnostic_info['installation_method']}",
|
|
188
|
+
f"Platform: {platform_info['os']} {platform_info['os_release']} ({platform_info['architecture']})",
|
|
189
|
+
f"Python Version: {platform_info['python_version']}",
|
|
190
|
+
f"Home Assistant Version: {diagnostic_info['home_assistant_version']}",
|
|
191
|
+
f"Connection Status: {diagnostic_info['connection_status']}",
|
|
192
|
+
f"Entity Count: {diagnostic_info['entity_count']}",
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
# Add optional fields if available
|
|
196
|
+
if "location_name" in diagnostic_info:
|
|
197
|
+
report_lines.append(f"Location Name: {diagnostic_info['location_name']}")
|
|
198
|
+
if "time_zone" in diagnostic_info:
|
|
199
|
+
report_lines.append(f"Time Zone: {diagnostic_info['time_zone']}")
|
|
200
|
+
|
|
201
|
+
if startup_logs:
|
|
202
|
+
report_lines.extend([
|
|
203
|
+
"",
|
|
204
|
+
f"=== Startup Logs ({len(startup_logs)} entries) ===",
|
|
205
|
+
startup_log_summary,
|
|
206
|
+
])
|
|
207
|
+
|
|
208
|
+
if recent_logs:
|
|
209
|
+
report_lines.extend([
|
|
210
|
+
"",
|
|
211
|
+
f"=== Recent Tool Calls ({len(recent_logs)} entries) ===",
|
|
212
|
+
log_summary,
|
|
213
|
+
])
|
|
214
|
+
|
|
215
|
+
formatted_report = "\n".join(report_lines)
|
|
216
|
+
|
|
217
|
+
# Generate BOTH templates
|
|
218
|
+
runtime_bug_template = _generate_runtime_bug_template(
|
|
219
|
+
diagnostic_info, log_summary, startup_log_summary, recent_logs, startup_logs
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
agent_behavior_template = _generate_agent_behavior_template(
|
|
223
|
+
diagnostic_info, log_summary, recent_logs
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Anonymization instructions
|
|
227
|
+
anonymization_guide = _generate_anonymization_guide()
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
"success": True,
|
|
231
|
+
"diagnostic_info": diagnostic_info,
|
|
232
|
+
"recent_logs": recent_logs,
|
|
233
|
+
"startup_logs": startup_logs,
|
|
234
|
+
"log_count": len(recent_logs),
|
|
235
|
+
"startup_log_count": len(startup_logs),
|
|
236
|
+
"formatted_report": formatted_report,
|
|
237
|
+
"runtime_bug_template": runtime_bug_template,
|
|
238
|
+
"agent_behavior_template": agent_behavior_template,
|
|
239
|
+
"anonymization_guide": anonymization_guide,
|
|
240
|
+
"instructions": (
|
|
241
|
+
"ANALYZE THE CONVERSATION to determine which template to present:\n\n"
|
|
242
|
+
"🐛 Present RUNTIME_BUG_TEMPLATE if:\n"
|
|
243
|
+
" - User reports an error, failure, or unexpected behavior in ha-mcp\n"
|
|
244
|
+
" - A tool returned an error or incorrect result\n"
|
|
245
|
+
" - Something is broken or not working\n"
|
|
246
|
+
" Submit at: https://github.com/homeassistant-ai/ha-mcp/issues/new?template=runtime_bug.md\n\n"
|
|
247
|
+
"🤖 Present AGENT_BEHAVIOR_TEMPLATE if:\n"
|
|
248
|
+
" - User mentions YOU (the agent) used the wrong tool\n"
|
|
249
|
+
" - User suggests YOU should have done something differently\n"
|
|
250
|
+
" - User reports YOUR inefficiency or mistakes\n"
|
|
251
|
+
" Submit at: https://github.com/homeassistant-ai/ha-mcp/issues/new?template=agent_behavior_feedback.md\n\n"
|
|
252
|
+
"If UNCLEAR which type, ASK: 'Are you reporting a bug in ha-mcp, or providing feedback on how I used the tools?'\n\n"
|
|
253
|
+
"Present the chosen template to the user. Ask them to fill in the description sections. "
|
|
254
|
+
"Remind them to follow the anonymization_guide to protect their privacy."
|
|
255
|
+
),
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _format_logs_for_report(logs: list[dict[str, Any]]) -> str:
|
|
260
|
+
"""Format log entries for inclusion in a bug report."""
|
|
261
|
+
if not logs:
|
|
262
|
+
return "(No recent logs available)"
|
|
263
|
+
|
|
264
|
+
lines = []
|
|
265
|
+
for log in logs:
|
|
266
|
+
timestamp = log.get("timestamp", "?")[:19] # Trim to seconds
|
|
267
|
+
tool_name = log.get("tool_name", "unknown")
|
|
268
|
+
success = "OK" if log.get("success") else "FAIL"
|
|
269
|
+
exec_time = log.get("execution_time_ms", 0)
|
|
270
|
+
error = log.get("error_message", "")
|
|
271
|
+
|
|
272
|
+
line = f" {timestamp} | {tool_name} | {success} | {exec_time:.0f}ms"
|
|
273
|
+
if error:
|
|
274
|
+
# Truncate error to avoid leaking sensitive info
|
|
275
|
+
error_short = str(error)[:100]
|
|
276
|
+
line += f" | Error: {error_short}"
|
|
277
|
+
lines.append(line)
|
|
278
|
+
|
|
279
|
+
return "\n".join(lines)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _format_startup_logs(logs: list[dict[str, Any]]) -> str:
|
|
283
|
+
"""Format startup log entries for inclusion in a bug report."""
|
|
284
|
+
if not logs:
|
|
285
|
+
return "(No startup logs available)"
|
|
286
|
+
|
|
287
|
+
lines = []
|
|
288
|
+
for log in logs:
|
|
289
|
+
elapsed = log.get("elapsed_seconds", 0)
|
|
290
|
+
level = log.get("level", "INFO")
|
|
291
|
+
logger_name = log.get("logger", "")
|
|
292
|
+
message = log.get("message", "")
|
|
293
|
+
|
|
294
|
+
# Truncate long messages
|
|
295
|
+
if len(message) > 200:
|
|
296
|
+
message = message[:200] + "..."
|
|
297
|
+
|
|
298
|
+
line = f" +{elapsed:05.2f}s | {level:5} | {logger_name}: {message}"
|
|
299
|
+
lines.append(line)
|
|
300
|
+
|
|
301
|
+
return "\n".join(lines)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _extract_error_messages(logs: list[dict[str, Any]]) -> list[str]:
|
|
305
|
+
"""
|
|
306
|
+
Extract error messages from tool call logs.
|
|
307
|
+
|
|
308
|
+
Returns a list of error messages with context (tool name, timestamp).
|
|
309
|
+
"""
|
|
310
|
+
if not logs:
|
|
311
|
+
return []
|
|
312
|
+
|
|
313
|
+
error_messages = []
|
|
314
|
+
for log in logs:
|
|
315
|
+
error = log.get("error_message")
|
|
316
|
+
if error:
|
|
317
|
+
timestamp = log.get("timestamp", "?")[:19] # Trim to seconds
|
|
318
|
+
tool_name = log.get("tool_name", "unknown")
|
|
319
|
+
# Format: [timestamp] tool_name: error_message
|
|
320
|
+
error_messages.append(f"[{timestamp}] {tool_name}: {error}")
|
|
321
|
+
|
|
322
|
+
return error_messages
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _generate_runtime_bug_template(
|
|
326
|
+
diagnostic_info: dict[str, Any],
|
|
327
|
+
log_summary: str,
|
|
328
|
+
startup_log_summary: str,
|
|
329
|
+
recent_logs: list[dict[str, Any]],
|
|
330
|
+
startup_logs: list[dict[str, Any]],
|
|
331
|
+
) -> str:
|
|
332
|
+
"""
|
|
333
|
+
Generate a runtime bug report template matching runtime_bug.md format.
|
|
334
|
+
|
|
335
|
+
This template matches the GitHub issue template EXACTLY so users can
|
|
336
|
+
copy-paste without format conflicts.
|
|
337
|
+
"""
|
|
338
|
+
platform_info = diagnostic_info.get("platform", {})
|
|
339
|
+
|
|
340
|
+
# Extract error messages from recent logs
|
|
341
|
+
error_messages = _extract_error_messages(recent_logs)
|
|
342
|
+
error_section = "\n".join(error_messages) if error_messages else "<!-- No errors detected in recent logs -->"
|
|
343
|
+
|
|
344
|
+
# Show startup logs section only if they exist
|
|
345
|
+
startup_section = ""
|
|
346
|
+
if startup_logs:
|
|
347
|
+
startup_section = f"""
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## 🚀 Startup Logs (if relevant)
|
|
351
|
+
|
|
352
|
+
<details>
|
|
353
|
+
<summary>Click to expand startup logs</summary>
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
{startup_log_summary}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
</details>
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
return f"""## 🚨 Auto-Generated by `ha_report_issue` Tool
|
|
363
|
+
|
|
364
|
+
> This template was auto-generated by the ha_report_issue tool.
|
|
365
|
+
> All environment info and logs below were collected automatically.
|
|
366
|
+
|
|
367
|
+
**Submit this report at:**
|
|
368
|
+
https://github.com/homeassistant-ai/ha-mcp/issues/new?template=runtime_bug.md
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## 📋 Bug Description
|
|
373
|
+
<!-- ONE clear sentence: What went wrong? -->
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
## 🔄 Steps to Reproduce
|
|
377
|
+
1.
|
|
378
|
+
2.
|
|
379
|
+
3.
|
|
380
|
+
|
|
381
|
+
## ✅ Expected vs ❌ Actual Behavior
|
|
382
|
+
|
|
383
|
+
**Expected:**
|
|
384
|
+
<!-- What should have happened? -->
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
**Actual:**
|
|
388
|
+
<!-- What actually happened? -->
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## 🔧 Environment
|
|
394
|
+
|
|
395
|
+
- **ha-mcp Version:** {diagnostic_info.get('ha_mcp_version', 'Unknown')}
|
|
396
|
+
- **Installation Method:** {diagnostic_info.get('installation_method', 'Unknown')}
|
|
397
|
+
- **Platform:** {platform_info.get('os', 'Unknown')} {platform_info.get('os_release', '')} ({platform_info.get('architecture', 'Unknown')})
|
|
398
|
+
- **Python Version:** {platform_info.get('python_version', 'Unknown')}
|
|
399
|
+
- **Home Assistant Version:** {diagnostic_info.get('home_assistant_version', 'Unknown')}
|
|
400
|
+
- **Connection Status:** {diagnostic_info.get('connection_status', 'Unknown')}
|
|
401
|
+
- **Entity Count:** {diagnostic_info.get('entity_count', 0)}
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## 🚨 Error Messages
|
|
406
|
+
|
|
407
|
+
```
|
|
408
|
+
{error_section}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## 📊 Recent Tool Calls
|
|
414
|
+
|
|
415
|
+
<details>
|
|
416
|
+
<summary>Click to expand recent tool calls (auto-filled by ha_report_issue)</summary>
|
|
417
|
+
|
|
418
|
+
```
|
|
419
|
+
{log_summary}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
</details>
|
|
423
|
+
{startup_section}
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## 💡 Additional Context
|
|
427
|
+
|
|
428
|
+
<!-- Any other relevant information: -->
|
|
429
|
+
<!-- - Suggested fixes -->
|
|
430
|
+
<!-- - Workarounds you found -->
|
|
431
|
+
<!-- - Related issues -->
|
|
432
|
+
<!-- - Configuration snippets -->
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
**Privacy reminder:** Please review and anonymize sensitive information (tokens, IPs, personal names) before submitting.
|
|
438
|
+
"""
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _generate_agent_behavior_template(
|
|
442
|
+
diagnostic_info: dict[str, Any],
|
|
443
|
+
log_summary: str,
|
|
444
|
+
recent_logs: list[dict[str, Any]],
|
|
445
|
+
) -> str:
|
|
446
|
+
"""
|
|
447
|
+
Generate an agent behavior feedback template matching agent_behavior_feedback.md format.
|
|
448
|
+
|
|
449
|
+
This template focuses on AI agent tool usage patterns and inefficiencies.
|
|
450
|
+
"""
|
|
451
|
+
platform_info = diagnostic_info.get("platform", {})
|
|
452
|
+
|
|
453
|
+
return f"""## 🤖 Auto-Generated by `ha_report_issue` Tool
|
|
454
|
+
|
|
455
|
+
> This template was auto-generated by the ha_report_issue tool.
|
|
456
|
+
> Tool call history was collected automatically to help analyze agent behavior.
|
|
457
|
+
|
|
458
|
+
**Submit this feedback at:**
|
|
459
|
+
https://github.com/homeassistant-ai/ha-mcp/issues/new?template=agent_behavior_feedback.md
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## 🤖 What Did the AI Agent Do?
|
|
464
|
+
|
|
465
|
+
<!-- Describe what the AI agent did that could be improved -->
|
|
466
|
+
<!-- Examples: -->
|
|
467
|
+
<!-- - Used the wrong tool initially, then corrected itself -->
|
|
468
|
+
<!-- - Provided invalid parameters to a tool -->
|
|
469
|
+
<!-- - Made multiple unnecessary tool calls -->
|
|
470
|
+
<!-- - Missed an obvious shortcut or better approach -->
|
|
471
|
+
<!-- - Misinterpreted tool output -->
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
## 🎯 What Should the Agent Have Done?
|
|
475
|
+
|
|
476
|
+
<!-- Describe the more efficient or correct approach -->
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
## 📝 Conversation Context
|
|
480
|
+
|
|
481
|
+
<!-- Provide context about what you were trying to do -->
|
|
482
|
+
<!-- Example: "I asked the agent to create an automation that..." -->
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## 🔧 Tool Calls Made (Auto-Filled)
|
|
488
|
+
|
|
489
|
+
<details>
|
|
490
|
+
<summary>Click to expand tool call sequence</summary>
|
|
491
|
+
|
|
492
|
+
```
|
|
493
|
+
{log_summary}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
</details>
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## 💡 Suggested Improvement
|
|
501
|
+
|
|
502
|
+
<!-- How could the agent be improved? Options: -->
|
|
503
|
+
|
|
504
|
+
- [ ] **Tool documentation** - Tool description or examples need clarification
|
|
505
|
+
- [ ] **Error messages** - Tool should return better guidance on failure
|
|
506
|
+
- [ ] **Tool design** - Tool should accept different parameters or return more info
|
|
507
|
+
- [ ] **Agent prompting** - System prompt should guide agent differently
|
|
508
|
+
- [ ] **New tool needed** - Missing functionality requires a new tool
|
|
509
|
+
- [ ] **Other** - Describe below
|
|
510
|
+
|
|
511
|
+
**Details:**
|
|
512
|
+
<!-- Explain your suggestion -->
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## 📊 Environment (Optional)
|
|
518
|
+
|
|
519
|
+
- **ha-mcp Version:** {diagnostic_info.get('ha_mcp_version', 'Unknown')}
|
|
520
|
+
- **AI Client:** (Claude Desktop / Claude Code / Other)
|
|
521
|
+
- **Home Assistant Version:** {diagnostic_info.get('home_assistant_version', 'Unknown')}
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## 📎 Additional Context
|
|
526
|
+
|
|
527
|
+
<!-- Screenshots, conversation logs, or other helpful info -->
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
**Note:** This is for improving AI agent behavior. For ha-mcp bugs (errors, crashes), use the Runtime Bug template instead.
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def _generate_anonymization_guide() -> str:
|
|
537
|
+
"""Generate privacy/anonymization instructions."""
|
|
538
|
+
return """## Anonymization Guide
|
|
539
|
+
|
|
540
|
+
Before submitting your bug report, please review and anonymize:
|
|
541
|
+
|
|
542
|
+
### MUST ANONYMIZE (security-sensitive):
|
|
543
|
+
- API tokens, passwords, secrets -> Replace with "[REDACTED]"
|
|
544
|
+
- IP addresses (internal/external) -> Replace with "192.168.x.x" or "[IP]"
|
|
545
|
+
- MAC addresses -> Replace with "[MAC]"
|
|
546
|
+
- Email addresses -> Replace with "user@example.com"
|
|
547
|
+
- Phone numbers -> Replace with "[PHONE]"
|
|
548
|
+
|
|
549
|
+
### CONSIDER ANONYMIZING (privacy-sensitive):
|
|
550
|
+
- Location names (city, address) -> Replace with generic names like "Home" or "[LOCATION]"
|
|
551
|
+
- Device names that reveal personal info -> Replace with "Device 1", "Light 1", etc.
|
|
552
|
+
- Person names in entity IDs -> Replace with "person.user1"
|
|
553
|
+
- Calendar/todo items with personal details -> Summarize without specifics
|
|
554
|
+
|
|
555
|
+
### KEEP AS-IS (helpful for debugging):
|
|
556
|
+
- Entity domains (light, switch, sensor, etc.)
|
|
557
|
+
- Device types and capabilities
|
|
558
|
+
- Automation/script structure (triggers, conditions, actions)
|
|
559
|
+
- Error messages (but check for secrets in them)
|
|
560
|
+
- Timestamps and durations
|
|
561
|
+
- State values (on/off, numeric values, etc.)
|
|
562
|
+
- Home Assistant and ha-mcp versions
|
|
563
|
+
|
|
564
|
+
### Example anonymization:
|
|
565
|
+
BEFORE: "light.juliens_bedroom" with token "eyJhbG..."
|
|
566
|
+
AFTER: "light.bedroom_1" with token "[REDACTED]"
|
|
567
|
+
|
|
568
|
+
The goal is to preserve enough detail to reproduce and fix the bug
|
|
569
|
+
while protecting your personal information and security.
|
|
570
|
+
"""
|