ha-mcp-dev 7.13.0.dev2029__tar.gz → 7.13.0.dev2033__tar.gz
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_dev-7.13.0.dev2029/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.13.0.dev2033}/PKG-INFO +1 -1
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/pyproject.toml +1 -1
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/read_only.py +7 -4
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/smart_search/_deep.py +97 -10
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/smart_search/_fetch.py +89 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/smart_search/_scenes.py +33 -6
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/LICENSE +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/README.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/setup.cfg +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/locales/en.json +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/tests/test_env_manager.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ha-mcp-dev"
|
|
7
|
-
version = "7.13.0.
|
|
7
|
+
version = "7.13.0.dev2033"
|
|
8
8
|
description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# Upper bound widened to <3.15 so the package installs on Home Assistant's
|
|
@@ -66,9 +66,11 @@ class ReadOnlyExemption(NamedTuple):
|
|
|
66
66
|
def _backup_write(args: dict[str, Any]) -> str | None:
|
|
67
67
|
scope = args.get("scope")
|
|
68
68
|
action = args.get("action")
|
|
69
|
-
# Read-only-safe: per-edit backup listing/viewing, and snapshot listing
|
|
69
|
+
# Read-only-safe: per-edit backup listing/viewing/diffing, and snapshot listing
|
|
70
70
|
# (issue #1586 — pure ``backup/info`` read, no tarball mutation).
|
|
71
|
-
|
|
71
|
+
# ``diff`` (added in #1632) only reads the stored snapshot and fetches
|
|
72
|
+
# the live config to compare; it never mutates (issue #1944).
|
|
73
|
+
if scope == "edits" and action in ("list", "view", "diff"):
|
|
72
74
|
return None
|
|
73
75
|
if scope == "snapshot" and action == "list":
|
|
74
76
|
return None
|
|
@@ -195,8 +197,9 @@ READ_ONLY_EXEMPT_TOOLS: dict[str, ReadOnlyExemption] = {
|
|
|
195
197
|
),
|
|
196
198
|
"ha_manage_backup": ReadOnlyExemption(
|
|
197
199
|
_backup_write,
|
|
198
|
-
"listing and
|
|
199
|
-
"'view') and listing snapshots
|
|
200
|
+
"listing, viewing, and diffing per-edit backups (scope='edits', "
|
|
201
|
+
"action='list', 'view', or 'diff') and listing snapshots "
|
|
202
|
+
"(scope='snapshot', action='list')",
|
|
200
203
|
),
|
|
201
204
|
"ha_manage_addon": ReadOnlyExemption(
|
|
202
205
|
_addon_write,
|
{ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/smart_search/_deep.py
RENAMED
|
@@ -21,7 +21,11 @@ from ._config import (
|
|
|
21
21
|
INDIVIDUAL_FETCH_BATCH_SIZE,
|
|
22
22
|
SCRIPT_CONFIG_TIME_BUDGET,
|
|
23
23
|
)
|
|
24
|
-
from ._fetch import
|
|
24
|
+
from ._fetch import (
|
|
25
|
+
http_500_diagnosis_hint,
|
|
26
|
+
is_timeout_error,
|
|
27
|
+
record_first_failure,
|
|
28
|
+
)
|
|
25
29
|
from ._scenes import SceneSearchMixin
|
|
26
30
|
|
|
27
31
|
logger = logging.getLogger(__name__)
|
|
@@ -136,6 +140,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
136
140
|
"timeout": 0,
|
|
137
141
|
"integration_skipped": 0,
|
|
138
142
|
"registry_failed": False,
|
|
143
|
+
"failed_sample": None,
|
|
139
144
|
}
|
|
140
145
|
|
|
141
146
|
# Diagnostic counters for the Attempt-C path on automations/
|
|
@@ -149,10 +154,12 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
149
154
|
automation_failed = 0
|
|
150
155
|
automation_yaml_skipped = 0
|
|
151
156
|
automation_timeout = 0
|
|
157
|
+
automation_failed_sample: str | None = None
|
|
152
158
|
script_skipped = 0
|
|
153
159
|
script_failed = 0
|
|
154
160
|
script_yaml_skipped = 0
|
|
155
161
|
script_timeout = 0
|
|
162
|
+
script_failed_sample: str | None = None
|
|
156
163
|
helper_failed = 0
|
|
157
164
|
dashboard_failed = 0
|
|
158
165
|
|
|
@@ -163,6 +170,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
163
170
|
automation_failed,
|
|
164
171
|
automation_yaml_skipped,
|
|
165
172
|
automation_timeout,
|
|
173
|
+
automation_failed_sample,
|
|
166
174
|
) = await self._deep_search_automations(
|
|
167
175
|
all_entities,
|
|
168
176
|
automation_unique_id_map,
|
|
@@ -185,6 +193,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
185
193
|
script_failed,
|
|
186
194
|
script_yaml_skipped,
|
|
187
195
|
script_timeout,
|
|
196
|
+
script_failed_sample,
|
|
188
197
|
) = await self._deep_search_scripts(
|
|
189
198
|
all_entities,
|
|
190
199
|
query_lower,
|
|
@@ -207,6 +216,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
207
216
|
scene_stats["integration_skipped"],
|
|
208
217
|
scene_stats["registry_failed"],
|
|
209
218
|
scene_stats["timeout"],
|
|
219
|
+
scene_stats["failed_sample"],
|
|
210
220
|
) = await self._deep_search_scenes(
|
|
211
221
|
all_entities,
|
|
212
222
|
query_lower,
|
|
@@ -268,10 +278,12 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
268
278
|
automation_failed=automation_failed,
|
|
269
279
|
automation_yaml_skipped=automation_yaml_skipped,
|
|
270
280
|
automation_timeout=automation_timeout,
|
|
281
|
+
automation_failed_sample=automation_failed_sample,
|
|
271
282
|
script_skipped=script_skipped,
|
|
272
283
|
script_failed=script_failed,
|
|
273
284
|
script_yaml_skipped=script_yaml_skipped,
|
|
274
285
|
script_timeout=script_timeout,
|
|
286
|
+
script_failed_sample=script_failed_sample,
|
|
275
287
|
helper_failed=helper_failed,
|
|
276
288
|
dashboard_failed=dashboard_failed,
|
|
277
289
|
)
|
|
@@ -318,11 +330,11 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
318
330
|
exact_match: bool,
|
|
319
331
|
*,
|
|
320
332
|
config_time_budget: float | None = None,
|
|
321
|
-
) -> tuple[list[dict[str, Any]], int, int, int, int]:
|
|
333
|
+
) -> tuple[list[dict[str, Any]], int, int, int, int, str | None]:
|
|
322
334
|
"""Deep-search automations: two-tier config fetch (REST bulk -> budgeted individual).
|
|
323
335
|
|
|
324
336
|
Returns ``(matches, skipped_count, failed_count, yaml_skipped_count,
|
|
325
|
-
timeout_count)``. ``skipped_count`` is non-zero only when bulk fetch
|
|
337
|
+
timeout_count, failed_sample)``. ``skipped_count`` is non-zero only when bulk fetch
|
|
326
338
|
fell back to the per-id Attempt-C path AND its wall-clock budget
|
|
327
339
|
exhausted before all configs were fetched; ``failed_count`` is
|
|
328
340
|
non-zero when individual config fetches raised a non-404, non-timeout
|
|
@@ -338,6 +350,11 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
338
350
|
distinguish a complete zero-result from an incomplete one —
|
|
339
351
|
skipped/failed ids carry their score-without-config through the
|
|
340
352
|
merge and fall below the match threshold silently otherwise.
|
|
353
|
+
``failed_sample`` is one representative
|
|
354
|
+
``summarize_fetch_error`` summary of a ``failed``-class exception
|
|
355
|
+
(``None`` when none occurred), surfaced in the failed fragment so
|
|
356
|
+
the response names WHAT raised instead of pointing at debug logs
|
|
357
|
+
(#1784 follow-up).
|
|
341
358
|
"""
|
|
342
359
|
automation_entities = [
|
|
343
360
|
e for e in all_entities if e.get("entity_id", "").startswith("automation.")
|
|
@@ -376,6 +393,13 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
376
393
|
failed_count = 0
|
|
377
394
|
yaml_skipped_count = 0
|
|
378
395
|
timeout_count = 0
|
|
396
|
+
# One representative summary — only the FIRST ``failed``-class
|
|
397
|
+
# exception is kept (the fetch closure guards the append); it rides
|
|
398
|
+
# partial_reason as an ``e.g.`` (#1784 follow-up). The remaining
|
|
399
|
+
# failures are counted (``failed_count``) but not summarized, so the
|
|
400
|
+
# motivating "every per-id fetch 500s" case does N-1 fewer
|
|
401
|
+
# ``summarize_fetch_error`` calls.
|
|
402
|
+
failed_errors: list[str] = []
|
|
379
403
|
if not bulk_fetched:
|
|
380
404
|
uids_to_fetch = [
|
|
381
405
|
uid for _, _, uid, _ in scored if uid and uid not in configs
|
|
@@ -405,6 +429,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
405
429
|
logger.debug(
|
|
406
430
|
f"Automation individual config fetch ({uid}) failed: {e}"
|
|
407
431
|
)
|
|
432
|
+
record_first_failure(failed_errors, e)
|
|
408
433
|
return (uid, None, "failed")
|
|
409
434
|
except TimeoutError:
|
|
410
435
|
# asyncio.wait_for hit INDIVIDUAL_CONFIG_TIMEOUT. Classify
|
|
@@ -430,6 +455,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
430
455
|
logger.debug(
|
|
431
456
|
f"Automation individual config fetch ({uid}) failed: {e}"
|
|
432
457
|
)
|
|
458
|
+
record_first_failure(failed_errors, e)
|
|
433
459
|
return (uid, None, "failed")
|
|
434
460
|
|
|
435
461
|
(
|
|
@@ -463,7 +489,14 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
463
489
|
scored, configs, query_lower, exact_match
|
|
464
490
|
)
|
|
465
491
|
]
|
|
466
|
-
return
|
|
492
|
+
return (
|
|
493
|
+
matches,
|
|
494
|
+
skipped_count,
|
|
495
|
+
failed_count,
|
|
496
|
+
yaml_skipped_count,
|
|
497
|
+
timeout_count,
|
|
498
|
+
failed_errors[0] if failed_errors else None,
|
|
499
|
+
)
|
|
467
500
|
|
|
468
501
|
async def _deep_search_scripts(
|
|
469
502
|
self,
|
|
@@ -472,11 +505,11 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
472
505
|
exact_match: bool,
|
|
473
506
|
*,
|
|
474
507
|
config_time_budget: float | None = None,
|
|
475
|
-
) -> tuple[list[dict[str, Any]], int, int, int, int]:
|
|
508
|
+
) -> tuple[list[dict[str, Any]], int, int, int, int, str | None]:
|
|
476
509
|
"""Deep-search scripts: same two-tier strategy as automations.
|
|
477
510
|
|
|
478
511
|
Returns ``(matches, skipped_count, failed_count, yaml_skipped_count,
|
|
479
|
-
timeout_count)``; semantics identical to ``_deep_search_automations``
|
|
512
|
+
timeout_count, failed_sample)``; semantics identical to ``_deep_search_automations``
|
|
480
513
|
— the 404 path catches YAML-defined scripts (which
|
|
481
514
|
``client.get_script_config`` re-raises as
|
|
482
515
|
``HomeAssistantAPIError(status_code=404)``).
|
|
@@ -514,6 +547,13 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
514
547
|
failed_count = 0
|
|
515
548
|
yaml_skipped_count = 0
|
|
516
549
|
timeout_count = 0
|
|
550
|
+
# One representative summary — only the FIRST ``failed``-class
|
|
551
|
+
# exception is kept (the fetch closure guards the append); it rides
|
|
552
|
+
# partial_reason as an ``e.g.`` (#1784 follow-up). The remaining
|
|
553
|
+
# failures are counted (``failed_count``) but not summarized, so the
|
|
554
|
+
# motivating "every per-id fetch 500s" case does N-1 fewer
|
|
555
|
+
# ``summarize_fetch_error`` calls.
|
|
556
|
+
failed_errors: list[str] = []
|
|
517
557
|
if not bulk_fetched:
|
|
518
558
|
sids_to_fetch = [
|
|
519
559
|
sid for _, _, sid, _ in scored if sid and sid not in configs
|
|
@@ -540,6 +580,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
540
580
|
)
|
|
541
581
|
return (sid, None, "yaml_skipped")
|
|
542
582
|
logger.debug(f"Script individual config fetch ({sid}) failed: {e}")
|
|
583
|
+
record_first_failure(failed_errors, e)
|
|
543
584
|
return (sid, None, "failed")
|
|
544
585
|
except TimeoutError:
|
|
545
586
|
# See _fetch_automation_config: per-request timeout under
|
|
@@ -559,6 +600,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
559
600
|
)
|
|
560
601
|
return (sid, None, "timeout")
|
|
561
602
|
logger.debug(f"Script individual config fetch ({sid}) failed: {e}")
|
|
603
|
+
record_first_failure(failed_errors, e)
|
|
562
604
|
return (sid, None, "failed")
|
|
563
605
|
|
|
564
606
|
(
|
|
@@ -593,7 +635,14 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
593
635
|
scored, configs, query_lower, exact_match
|
|
594
636
|
)
|
|
595
637
|
]
|
|
596
|
-
return
|
|
638
|
+
return (
|
|
639
|
+
matches,
|
|
640
|
+
skipped_count,
|
|
641
|
+
failed_count,
|
|
642
|
+
yaml_skipped_count,
|
|
643
|
+
timeout_count,
|
|
644
|
+
failed_errors[0] if failed_errors else None,
|
|
645
|
+
)
|
|
597
646
|
|
|
598
647
|
@staticmethod
|
|
599
648
|
def _build_helper_registry_map(
|
|
@@ -966,6 +1015,8 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
966
1015
|
script_yaml_skipped: int = 0,
|
|
967
1016
|
automation_timeout: int = 0,
|
|
968
1017
|
script_timeout: int = 0,
|
|
1018
|
+
automation_failed_sample: str | None = None,
|
|
1019
|
+
script_failed_sample: str | None = None,
|
|
969
1020
|
helper_failed: int = 0,
|
|
970
1021
|
dashboard_failed: int = 0,
|
|
971
1022
|
) -> dict[str, Any]:
|
|
@@ -1028,6 +1079,8 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
1028
1079
|
script_yaml_skipped=script_yaml_skipped,
|
|
1029
1080
|
automation_timeout=automation_timeout,
|
|
1030
1081
|
script_timeout=script_timeout,
|
|
1082
|
+
automation_failed_sample=automation_failed_sample,
|
|
1083
|
+
script_failed_sample=script_failed_sample,
|
|
1031
1084
|
helper_failed=helper_failed,
|
|
1032
1085
|
dashboard_failed=dashboard_failed,
|
|
1033
1086
|
)
|
|
@@ -1045,6 +1098,8 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
1045
1098
|
script_yaml_skipped: int = 0,
|
|
1046
1099
|
automation_timeout: int = 0,
|
|
1047
1100
|
script_timeout: int = 0,
|
|
1101
|
+
automation_failed_sample: str | None = None,
|
|
1102
|
+
script_failed_sample: str | None = None,
|
|
1048
1103
|
helper_failed: int = 0,
|
|
1049
1104
|
dashboard_failed: int = 0,
|
|
1050
1105
|
) -> None:
|
|
@@ -1077,6 +1132,15 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
1077
1132
|
rationalised away by blind agents who reported the result as
|
|
1078
1133
|
complete; the harder phrasing closes that gap.
|
|
1079
1134
|
|
|
1135
|
+
``automation_failed_sample`` / ``script_failed_sample`` carry one
|
|
1136
|
+
representative ``summarize_fetch_error`` summary for the generic
|
|
1137
|
+
``failed`` class, appended to its fragment as an ``e.g.`` so the
|
|
1138
|
+
response names WHAT raised instead of pointing at debug logs
|
|
1139
|
+
(#1784 follow-up). When that sample is an HTTP 500 — whose body is
|
|
1140
|
+
aiohttp's generic placeholder, so the sample can't name the cause —
|
|
1141
|
+
a static HA-log diagnosis is appended too (``http_500_diagnosis_hint``).
|
|
1142
|
+
``None`` keeps the fragment wording unchanged.
|
|
1143
|
+
|
|
1080
1144
|
Append-safe: the existing ``partial_reason`` (if any) is preserved
|
|
1081
1145
|
and the new reasons are concatenated with ``" ; "``.
|
|
1082
1146
|
"""
|
|
@@ -1084,7 +1148,16 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
1084
1148
|
# The automation and script fragments are symmetric (noun, per-id
|
|
1085
1149
|
# endpoint, budget env var); loop rather than duplicating the four
|
|
1086
1150
|
# per-class fragments per type.
|
|
1087
|
-
for
|
|
1151
|
+
for (
|
|
1152
|
+
noun,
|
|
1153
|
+
endpoint,
|
|
1154
|
+
budget_env,
|
|
1155
|
+
skipped,
|
|
1156
|
+
failed,
|
|
1157
|
+
yaml_skipped,
|
|
1158
|
+
timeout,
|
|
1159
|
+
failed_sample,
|
|
1160
|
+
) in (
|
|
1088
1161
|
(
|
|
1089
1162
|
"automation",
|
|
1090
1163
|
"/config/automation/config",
|
|
@@ -1093,6 +1166,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
1093
1166
|
automation_failed,
|
|
1094
1167
|
automation_yaml_skipped,
|
|
1095
1168
|
automation_timeout,
|
|
1169
|
+
automation_failed_sample,
|
|
1096
1170
|
),
|
|
1097
1171
|
(
|
|
1098
1172
|
"script",
|
|
@@ -1102,6 +1176,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
1102
1176
|
script_failed,
|
|
1103
1177
|
script_yaml_skipped,
|
|
1104
1178
|
script_timeout,
|
|
1179
|
+
script_failed_sample,
|
|
1105
1180
|
),
|
|
1106
1181
|
):
|
|
1107
1182
|
if skipped:
|
|
@@ -1114,10 +1189,22 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
1114
1189
|
"the web Settings UI's Advanced section)."
|
|
1115
1190
|
)
|
|
1116
1191
|
if failed:
|
|
1192
|
+
# Name ONE representative error inline when the fetch path
|
|
1193
|
+
# captured one — the opaque bucket alone sent users on a
|
|
1194
|
+
# debug-log dive for trivially-diagnosable server errors
|
|
1195
|
+
# (#1784 follow-up: every per-id script fetch 500ing on a
|
|
1196
|
+
# ``!secret`` reference in scripts.yaml).
|
|
1197
|
+
sample_suffix = f"; e.g. {failed_sample}" if failed_sample else ""
|
|
1198
|
+
# An HTTP 500 body is aiohttp's generic "500 Internal Server
|
|
1199
|
+
# Error" — the real cause (e.g. a ``!secret`` the per-id
|
|
1200
|
+
# config endpoint rejects) is HA-log-only, never in the
|
|
1201
|
+
# response — so the sample can't name it. Append the static
|
|
1202
|
+
# diagnosis the sample can't carry (#1784 follow-up).
|
|
1203
|
+
hint = http_500_diagnosis_hint(failed_sample)
|
|
1117
1204
|
reasons.append(
|
|
1118
1205
|
f"{failed} {noun}(s) not scanned (per-id fetch raised "
|
|
1119
|
-
"a non-404 error) — their match status
|
|
1120
|
-
"result is not exhaustive."
|
|
1206
|
+
f"a non-404 error{sample_suffix}) — their match status "
|
|
1207
|
+
f"is unknown; this result is not exhaustive.{hint}"
|
|
1121
1208
|
)
|
|
1122
1209
|
if yaml_skipped:
|
|
1123
1210
|
reasons.append(
|
{ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/smart_search/_fetch.py
RENAMED
|
@@ -2,17 +2,106 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import re
|
|
5
6
|
import time
|
|
6
7
|
from collections.abc import Awaitable, Callable
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
10
|
import httpx
|
|
10
11
|
|
|
12
|
+
from ...client.rest_client import HomeAssistantAPIError
|
|
11
13
|
from ._config import INDIVIDUAL_FETCH_BATCH_SIZE
|
|
12
14
|
from ._scoring import ScoringMixin
|
|
13
15
|
|
|
14
16
|
logger = logging.getLogger(__name__)
|
|
15
17
|
|
|
18
|
+
# The REST client's own message prefix ("API error: <code> - "); stripped by
|
|
19
|
+
# ``summarize_fetch_error`` so the summary doesn't state the status twice.
|
|
20
|
+
_API_ERROR_PREFIX = re.compile(r"^API error:\s*\d+\s*-\s*")
|
|
21
|
+
|
|
22
|
+
# Hard cap on a ``summarize_fetch_error`` summary. partial_reason fragments
|
|
23
|
+
# are read inline by agents; one representative error needs a first line,
|
|
24
|
+
# not a body dump.
|
|
25
|
+
_ERROR_SAMPLE_MAX_LEN = 160
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def summarize_fetch_error(exc: BaseException) -> str:
|
|
29
|
+
"""One-line, length-capped summary of a per-id config-fetch failure.
|
|
30
|
+
|
|
31
|
+
The generic ``failed`` bucket collapses every non-404, non-timeout
|
|
32
|
+
exception into one opaque partial_reason fragment ("per-id fetch raised
|
|
33
|
+
a non-404 error"), with the real exception visible only at debug-level
|
|
34
|
+
logging. The follow-up report on issue #1784 showed what that hides: a
|
|
35
|
+
box where every per-id script fetch returns a fast HTTP 500 because
|
|
36
|
+
``scripts.yaml`` contains a ``!secret`` reference (HA core's config view
|
|
37
|
+
file-loads the YAML per request and rejects secrets before the id
|
|
38
|
+
lookup) — a five-minute diagnosis if the response named the error, a
|
|
39
|
+
log dive otherwise. This summary rides the fragment as an ``e.g.``.
|
|
40
|
+
|
|
41
|
+
HTTP errors render as ``HTTP <code>: <first line of the message>`` (the
|
|
42
|
+
client's own ``API error: <code> - `` prefix is stripped rather than
|
|
43
|
+
duplicated); everything else as ``<ExceptionType>: <first line>``.
|
|
44
|
+
"""
|
|
45
|
+
lines = str(exc).strip().splitlines()
|
|
46
|
+
text = lines[0].strip() if lines else ""
|
|
47
|
+
if isinstance(exc, HomeAssistantAPIError) and exc.status_code is not None:
|
|
48
|
+
text = _API_ERROR_PREFIX.sub("", text)
|
|
49
|
+
prefix = f"HTTP {exc.status_code}"
|
|
50
|
+
else:
|
|
51
|
+
prefix = type(exc).__name__
|
|
52
|
+
summary = f"{prefix}: {text}" if text else prefix
|
|
53
|
+
if len(summary) > _ERROR_SAMPLE_MAX_LEN:
|
|
54
|
+
summary = summary[: _ERROR_SAMPLE_MAX_LEN - 1] + "…"
|
|
55
|
+
return summary
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Marks a rendered ``summarize_fetch_error`` summary whose cause the sample
|
|
59
|
+
# cannot name: an HTTP 500's body is aiohttp's generic "500 Internal Server
|
|
60
|
+
# Error" placeholder, so the real error (a YAMLException the per-id config
|
|
61
|
+
# view raised, most often a ``!secret`` reference it rejects) lives only in
|
|
62
|
+
# the HA log, never in the response.
|
|
63
|
+
_HTTP_500_PREFIX = "HTTP 500:"
|
|
64
|
+
|
|
65
|
+
# Static diagnosis appended to an HTTP-500 failed fragment. The sample names
|
|
66
|
+
# the status but not the cause (see ``_HTTP_500_PREFIX``); this points the
|
|
67
|
+
# reader at the HA log and the single most common cause, delivering the
|
|
68
|
+
# five-minute diagnosis the #1784 follow-up asked for without claiming a
|
|
69
|
+
# cause the response can't actually confirm. Kept endpoint-agnostic — this
|
|
70
|
+
# same string rides the automation, script, AND scene fragments, so it names
|
|
71
|
+
# no specific YAML file (a scene 500 has nothing to do with scripts.yaml);
|
|
72
|
+
# "the config file HA loads for this entity" covers all three.
|
|
73
|
+
_HTTP_500_DIAGNOSIS = (
|
|
74
|
+
" A per-id config fetch returning HTTP 500 is most often a `!secret` "
|
|
75
|
+
"reference in the config file HA loads for this entity, which the per-id "
|
|
76
|
+
"config endpoint rejects; the specific cause is in the Home Assistant log "
|
|
77
|
+
"(the 500 body is a generic placeholder), not in this response."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def http_500_diagnosis_hint(failed_sample: str | None) -> str:
|
|
82
|
+
"""Static HA-log hint to append when ``failed_sample`` is an HTTP 500.
|
|
83
|
+
|
|
84
|
+
Returns ``""`` for any other sample (or ``None``), so non-500 fragments
|
|
85
|
+
are byte-identical to before. See ``_HTTP_500_DIAGNOSIS``.
|
|
86
|
+
"""
|
|
87
|
+
if failed_sample and failed_sample.startswith(_HTTP_500_PREFIX):
|
|
88
|
+
return _HTTP_500_DIAGNOSIS
|
|
89
|
+
return ""
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def record_first_failure(failed_errors: list[str], exc: BaseException) -> None:
|
|
93
|
+
"""Capture ``exc``'s summary as the representative ``failed`` sample, once.
|
|
94
|
+
|
|
95
|
+
Only the FIRST generic-``failed`` exception per fetch pass is summarized
|
|
96
|
+
(subsequent ones are counted but not summarized) — in the motivating
|
|
97
|
+
"every per-id fetch 500s" case that is N-1 fewer ``summarize_fetch_error``
|
|
98
|
+
calls. The append is a no-op once ``failed_errors`` is non-empty. Called
|
|
99
|
+
from the per-id fetch closures, which run under ``asyncio.gather`` but
|
|
100
|
+
never suspend between the check and the append, so no lock is needed.
|
|
101
|
+
"""
|
|
102
|
+
if not failed_errors:
|
|
103
|
+
failed_errors.append(summarize_fetch_error(exc))
|
|
104
|
+
|
|
16
105
|
|
|
17
106
|
def is_timeout_error(exc: BaseException) -> bool:
|
|
18
107
|
"""True when ``exc`` is, or was directly caused by, a request timeout.
|
{ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/tools/smart_search/_scenes.py
RENAMED
|
@@ -10,7 +10,12 @@ from ._config import (
|
|
|
10
10
|
INDIVIDUAL_FETCH_BATCH_SIZE,
|
|
11
11
|
SCENE_CONFIG_TIME_BUDGET,
|
|
12
12
|
)
|
|
13
|
-
from ._fetch import
|
|
13
|
+
from ._fetch import (
|
|
14
|
+
ConfigFetchMixin,
|
|
15
|
+
http_500_diagnosis_hint,
|
|
16
|
+
is_timeout_error,
|
|
17
|
+
record_first_failure,
|
|
18
|
+
)
|
|
14
19
|
|
|
15
20
|
logger = logging.getLogger(__name__)
|
|
16
21
|
|
|
@@ -211,15 +216,19 @@ class SceneSearchMixin(ConfigFetchMixin):
|
|
|
211
216
|
*,
|
|
212
217
|
config_time_budget: float | None = None,
|
|
213
218
|
prefetched_registry: Any = None,
|
|
214
|
-
) -> tuple[list[dict[str, Any]], int, int, int, bool, int]:
|
|
219
|
+
) -> tuple[list[dict[str, Any]], int, int, int, bool, int, str | None]:
|
|
215
220
|
"""Deep-search scenes: two-tier strategy plus registry-walk augmentation.
|
|
216
221
|
|
|
217
222
|
Scenes have no listing primitive, so entities are enumerated from
|
|
218
223
|
get_states() and configs fetched per id. Returns the scene results plus
|
|
219
|
-
the
|
|
224
|
+
the six diagnostic signals feeding the response ``partial`` /
|
|
220
225
|
``partial_reason``:
|
|
221
226
|
``(results, failed_count, skipped_count, integration_skipped,
|
|
222
|
-
registry_failed, timeout_count)``.
|
|
227
|
+
registry_failed, timeout_count, failed_sample)``. ``failed_sample``
|
|
228
|
+
is one representative ``summarize_fetch_error`` summary of a
|
|
229
|
+
``failed``-class exception (``None`` when none occurred) — see the
|
|
230
|
+
automation/script mirror in ``_deep_search_automations`` (#1784
|
|
231
|
+
follow-up).
|
|
223
232
|
"""
|
|
224
233
|
scene_entities = [
|
|
225
234
|
e for e in all_entities if e.get("entity_id", "").startswith("scene.")
|
|
@@ -264,6 +273,11 @@ class SceneSearchMixin(ConfigFetchMixin):
|
|
|
264
273
|
skipped_count = 0
|
|
265
274
|
integration_skipped = 0
|
|
266
275
|
timeout_count = 0
|
|
276
|
+
# One representative summary — only the FIRST ``failed``-class
|
|
277
|
+
# exception is kept (the fetch closure guards the append); it rides
|
|
278
|
+
# partial_reason as an ``e.g.`` (#1784 follow-up). The remaining
|
|
279
|
+
# failures are counted (``failed_count``) but not summarized.
|
|
280
|
+
failed_errors: list[str] = []
|
|
267
281
|
|
|
268
282
|
# Attempt C: parallel per-id fetch with a wall-clock budget so a few
|
|
269
283
|
# slow scenes don't tank the whole search.
|
|
@@ -300,6 +314,7 @@ class SceneSearchMixin(ConfigFetchMixin):
|
|
|
300
314
|
)
|
|
301
315
|
return (sid, None, "timeout")
|
|
302
316
|
logger.debug(f"Scene individual config fetch ({sid}) failed: {e}")
|
|
317
|
+
record_first_failure(failed_errors, e)
|
|
303
318
|
return (sid, None, "failed")
|
|
304
319
|
|
|
305
320
|
(
|
|
@@ -347,6 +362,7 @@ class SceneSearchMixin(ConfigFetchMixin):
|
|
|
347
362
|
integration_skipped,
|
|
348
363
|
registry_failed,
|
|
349
364
|
timeout_count,
|
|
365
|
+
failed_errors[0] if failed_errors else None,
|
|
350
366
|
)
|
|
351
367
|
|
|
352
368
|
@staticmethod
|
|
@@ -376,9 +392,20 @@ class SceneSearchMixin(ConfigFetchMixin):
|
|
|
376
392
|
response["partial"] = True
|
|
377
393
|
reason_parts: list[str] = []
|
|
378
394
|
if failed:
|
|
395
|
+
# Name ONE representative error inline when Attempt C captured
|
|
396
|
+
# one (.get(): tolerate stats dicts built without the key) —
|
|
397
|
+
# mirrors the automation/script ``e.g.`` sample, #1784 follow-up.
|
|
398
|
+
failed_sample = scene_stats.get("failed_sample")
|
|
399
|
+
sample_suffix = f"; e.g. {failed_sample}" if failed_sample else ""
|
|
400
|
+
# An HTTP 500 sample names the status but not the cause (the body
|
|
401
|
+
# is aiohttp's generic placeholder); append the static HA-log
|
|
402
|
+
# diagnosis, mirroring the automation/script fragment (#1784).
|
|
403
|
+
hint = http_500_diagnosis_hint(failed_sample)
|
|
379
404
|
reason_parts.append(
|
|
380
|
-
f"{failed} scene(s) not scanned (per-id fetch raised
|
|
381
|
-
"
|
|
405
|
+
f"{failed} scene(s) not scanned (per-id fetch raised"
|
|
406
|
+
f"{sample_suffix}) — "
|
|
407
|
+
f"their match status is unknown; this result is not "
|
|
408
|
+
f"exhaustive.{hint}"
|
|
382
409
|
)
|
|
383
410
|
if timeout:
|
|
384
411
|
reason_parts.append(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/client/supervisor_client.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/client/websocket_client.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/client/websocket_listener.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/dashboard_screenshot/__init__.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/dashboard_screenshot/capture.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/dashboard_screenshot/content.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/dashboard_screenshot/paths.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/dashboard_screenshot/provision.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.13.0.dev2029 → ha_mcp_dev-7.13.0.dev2033}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|