ha-mcp-dev 7.12.3.dev1859__tar.gz → 7.12.3.dev1863__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.12.3.dev1859/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.12.3.dev1863}/PKG-INFO +1 -1
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/pyproject.toml +1 -1
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/config.py +56 -6
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/settings_ui/settings.js +6 -1
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/smart_search/_config.py +11 -5
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/smart_search/_deep.py +158 -81
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/smart_search/_fetch.py +49 -8
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/smart_search/_scenes.py +43 -5
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/LICENSE +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/README.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/setup.cfg +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/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.12.3.
|
|
7
|
+
version = "7.12.3.dev1863"
|
|
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
|
|
@@ -83,6 +83,22 @@ class Settings(BaseSettings):
|
|
|
83
83
|
20.0, alias="HAMCP_SCENE_CONFIG_TIME_BUDGET"
|
|
84
84
|
)
|
|
85
85
|
|
|
86
|
+
# Per-request timeout and concurrency of the smart-search per-id
|
|
87
|
+
# config-fetch fallback (Attempt C). On HA servers that serve
|
|
88
|
+
# /config/<domain>/config/<id> serially, a full batch of concurrent
|
|
89
|
+
# requests queues behind one another and the tail of each batch can
|
|
90
|
+
# exceed the per-request timeout even though every request would
|
|
91
|
+
# succeed — lowering the batch size (toward 1) and/or raising the
|
|
92
|
+
# timeout lets such instances scan exhaustively (issue #1784). Same
|
|
93
|
+
# consumption model as the budgets above: import-time constants in
|
|
94
|
+
# tools/smart_search/_config.py, restart required.
|
|
95
|
+
individual_config_timeout: float = Field(
|
|
96
|
+
5.0, alias="HAMCP_INDIVIDUAL_CONFIG_TIMEOUT"
|
|
97
|
+
)
|
|
98
|
+
individual_fetch_batch_size: int = Field(
|
|
99
|
+
10, alias="HAMCP_INDIVIDUAL_FETCH_BATCH_SIZE"
|
|
100
|
+
)
|
|
101
|
+
|
|
86
102
|
# Backup tool configuration
|
|
87
103
|
backup_hint: str = Field("normal", alias="BACKUP_HINT")
|
|
88
104
|
|
|
@@ -350,21 +366,26 @@ class Settings(BaseSettings):
|
|
|
350
366
|
"automation_config_time_budget",
|
|
351
367
|
"script_config_time_budget",
|
|
352
368
|
"scene_config_time_budget",
|
|
369
|
+
"individual_config_timeout",
|
|
370
|
+
"individual_fetch_batch_size",
|
|
353
371
|
mode="before",
|
|
354
372
|
)
|
|
355
373
|
@classmethod
|
|
356
374
|
def _lenient_time_budget(cls, v: object, info: ValidationInfo) -> object:
|
|
357
|
-
"""Coerce the
|
|
358
|
-
|
|
375
|
+
"""Coerce the smart-search Attempt-C knobs (the three time budgets,
|
|
376
|
+
the per-request timeout, and the fetch batch size), falling back to
|
|
377
|
+
the field default (with a warning) instead of crashing startup.
|
|
359
378
|
|
|
360
379
|
Preserves the parse-tolerance of the removed ``_env_float`` helper
|
|
361
380
|
(empty / unparseable -> default) and additionally enforces the same
|
|
362
381
|
``_ADVANCED_SETTINGS_BOUNDS`` range as the override-file / UI-POST
|
|
363
382
|
path, so the env-var path can't smuggle in an out-of-range or
|
|
364
|
-
non-finite
|
|
365
|
-
per-id config-fetch scan, and ``inf`` / ``nan`` would
|
|
366
|
-
``lo <= val <= hi`` test rejects all three (NaN
|
|
367
|
-
False), keeping the env and override-file paths
|
|
383
|
+
non-finite value. A ``<= 0`` budget or timeout would silently
|
|
384
|
+
disable the per-id config-fetch scan, and ``inf`` / ``nan`` would
|
|
385
|
+
uncap it; the ``lo <= val <= hi`` test rejects all three (NaN
|
|
386
|
+
comparisons are False), keeping the env and override-file paths
|
|
387
|
+
consistent. Int fields (batch size) additionally reject fractional
|
|
388
|
+
values rather than truncating them."""
|
|
368
389
|
field_name = info.field_name
|
|
369
390
|
if field_name is None: # always set for field_validator; defensive
|
|
370
391
|
return v
|
|
@@ -389,6 +410,17 @@ class Settings(BaseSettings):
|
|
|
389
410
|
default,
|
|
390
411
|
)
|
|
391
412
|
return default
|
|
413
|
+
if isinstance(default, int) and not isinstance(default, bool):
|
|
414
|
+
if val != int(val):
|
|
415
|
+
logger.warning(
|
|
416
|
+
"Invalid value for %s=%r (must be a whole number); "
|
|
417
|
+
"using default %s",
|
|
418
|
+
field_name,
|
|
419
|
+
v,
|
|
420
|
+
default,
|
|
421
|
+
)
|
|
422
|
+
return default
|
|
423
|
+
return int(val)
|
|
392
424
|
return val
|
|
393
425
|
|
|
394
426
|
@property
|
|
@@ -759,6 +791,22 @@ ADVANCED_SETTINGS_FIELDS: tuple[AdvancedField, ...] = (
|
|
|
759
791
|
"search",
|
|
760
792
|
True,
|
|
761
793
|
),
|
|
794
|
+
# Attempt-C per-request timeout + batch size (#1784). Restart-required
|
|
795
|
+
# (same import-time consumption as the budgets above).
|
|
796
|
+
AdvancedField(
|
|
797
|
+
"individual_config_timeout",
|
|
798
|
+
"HAMCP_INDIVIDUAL_CONFIG_TIMEOUT",
|
|
799
|
+
float,
|
|
800
|
+
"search",
|
|
801
|
+
True,
|
|
802
|
+
),
|
|
803
|
+
AdvancedField(
|
|
804
|
+
"individual_fetch_batch_size",
|
|
805
|
+
"HAMCP_INDIVIDUAL_FETCH_BATCH_SIZE",
|
|
806
|
+
int,
|
|
807
|
+
"search",
|
|
808
|
+
True,
|
|
809
|
+
),
|
|
762
810
|
# Operations.
|
|
763
811
|
AdvancedField("backup_hint", "BACKUP_HINT", str, "operations", True),
|
|
764
812
|
AdvancedField("enable_websocket", "ENABLE_WEBSOCKET", bool, "operations", True),
|
|
@@ -846,6 +894,8 @@ _ADVANCED_SETTINGS_BOUNDS: dict[str, tuple[float, float]] = {
|
|
|
846
894
|
"automation_config_time_budget": (1.0, 600.0),
|
|
847
895
|
"script_config_time_budget": (1.0, 600.0),
|
|
848
896
|
"scene_config_time_budget": (1.0, 600.0),
|
|
897
|
+
"individual_config_timeout": (1.0, 600.0),
|
|
898
|
+
"individual_fetch_batch_size": (1, 100),
|
|
849
899
|
"code_mode_max_duration": (1.0, 300.0),
|
|
850
900
|
"code_mode_max_memory": (1_048_576, 268_435_456),
|
|
851
901
|
"code_mode_max_recursion": (1, 10_000),
|
|
@@ -1553,7 +1553,7 @@ const FEATURE_META = {
|
|
|
1553
1553
|
},
|
|
1554
1554
|
enable_code_mode: {
|
|
1555
1555
|
label: "Enable code-mode sandbox (beta)",
|
|
1556
|
-
help: "Beta feature, disabled by default. Enables ha_manage_custom_tool, a sandboxed Python interpreter (pydantic-monty) that lets AI assistants write/run/save/delete custom tools when no built-in tool covers the request. Sandbox cannot touch the filesystem or arbitrary network, but CAN call any registered MCP tool, hit the HA REST API, or send HA WebSocket commands, effectively 'do whatever existing tools allow you to do, in any combination'. See docs/beta.md for known limitations. Requires restart to take effect.",
|
|
1556
|
+
help: "Beta feature, disabled by default. Enables ha_manage_custom_tool, a sandboxed Python interpreter (pydantic-monty) that lets AI assistants write/run/save/delete custom tools when no built-in tool covers the request. Sandbox cannot touch the filesystem or arbitrary network, but CAN call any registered MCP tool, hit the HA REST API, or send HA WebSocket commands, effectively 'do whatever existing tools allow you to do, in any combination'. Saved tools persist and are visible to any client that can connect to ha-mcp. See docs/beta.md for known limitations. Requires restart to take effect.",
|
|
1557
1557
|
},
|
|
1558
1558
|
enable_lite_docstrings: {
|
|
1559
1559
|
label: "Enable lite tool docstrings (beta)",
|
|
@@ -2946,6 +2946,8 @@ const ADVANCED_FIELD_META = {
|
|
|
2946
2946
|
automation_config_time_budget: { label: "Automation config time budget (s)", help: "Max seconds ha_search/ha_deep_search spends fetching automation configs before returning a partial result. Raise on instances with many automations. Range 1–600. Restart required." },
|
|
2947
2947
|
script_config_time_budget: { label: "Script config time budget (s)", help: "Max seconds ha_search/ha_deep_search spends fetching script configs before returning a partial result. Range 1–600. Restart required." },
|
|
2948
2948
|
scene_config_time_budget: { label: "Scene config time budget (s)", help: "Max seconds ha_search/ha_deep_search spends fetching scene configs before returning a partial result. Range 1–600. Restart required." },
|
|
2949
|
+
individual_config_timeout: { label: "Per-request config fetch timeout (s)", help: "Timeout for each individual automation/script/scene config fetch during deep search. On HA servers that serve config reads serially, raise this and/or lower the batch size so queued requests don't time out. Values above the HA request timeout (HA_TIMEOUT, default 30) have no extra effect — the HTTP client gives up first. Range 1–600. Restart required." },
|
|
2950
|
+
individual_fetch_batch_size: { label: "Config fetch batch size", help: "How many per-id config fetches deep search issues concurrently. Lower toward 1 on HA servers that serve config reads serially (symptom: 'timed out' partial-result warnings). Range 1–100. Restart required." },
|
|
2949
2951
|
backup_hint: { label: "Backup-hint level", help: "Tunes how strongly the LLM is prompted to take a full-HA snapshot before risky writes." },
|
|
2950
2952
|
dashboard_screenshot_engine_url: { label: "Dashboard screenshot engine URL", help: "Base URL of the screenshot engine (e.g. http://puppet:10000). Leave blank to auto-discover the Puppet App (add-on) via the Supervisor (HA OS / Supervised). Only used when the Dashboard Screenshot beta feature is enabled. Takes effect without a restart." },
|
|
2951
2953
|
enable_websocket: { label: "Enable WebSocket", help: "WebSocket-based state monitoring. Disabling falls back to polling; many tools degrade. Restart required." },
|
|
@@ -2985,6 +2987,9 @@ const ADVANCED_RESTART_REQUIRED = new Set([
|
|
|
2985
2987
|
// it is resolved live per capture, so it takes effect immediately.
|
|
2986
2988
|
"automation_config_time_budget", "script_config_time_budget",
|
|
2987
2989
|
"scene_config_time_budget",
|
|
2990
|
+
// The Attempt-C per-request timeout and batch size (#1784) share the
|
|
2991
|
+
// budgets' import-time consumption, so they need a restart too.
|
|
2992
|
+
"individual_config_timeout", "individual_fetch_batch_size",
|
|
2988
2993
|
"code_mode_max_duration", "code_mode_max_memory",
|
|
2989
2994
|
"code_mode_max_recursion", "code_mode_max_invocations",
|
|
2990
2995
|
"code_mode_saved_tools_path",
|
{ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/smart_search/_config.py
RENAMED
|
@@ -13,23 +13,29 @@ DEFAULT_CONCURRENCY_LIMIT = 20
|
|
|
13
13
|
# Bulk fetch timeouts (in seconds)
|
|
14
14
|
BULK_REST_TIMEOUT = 5.0 # Timeout for bulk REST endpoint calls
|
|
15
15
|
BULK_WEBSOCKET_TIMEOUT = 3.0 # Timeout for bulk WebSocket calls
|
|
16
|
-
INDIVIDUAL_CONFIG_TIMEOUT = 5.0 # Timeout for individual config fetches
|
|
17
16
|
|
|
18
17
|
|
|
19
|
-
#
|
|
20
|
-
#
|
|
18
|
+
# Attempt-C (per-id fallback) tuning knobs. Sourced from the resolved
|
|
19
|
+
# Settings (issues #1538 / #1784) so the env var, the web Settings UI
|
|
21
20
|
# override file, and the field defaults all flow through one precedence path
|
|
22
21
|
# — and so add-on users (who cannot set raw env vars) can tune them from the
|
|
23
22
|
# Advanced panel. Read once at import as module-level constants; a change
|
|
24
23
|
# takes effect on the next MCP-host restart (advanced settings already
|
|
25
24
|
# carry a restart-required notice in the UI).
|
|
26
25
|
_settings = get_global_settings()
|
|
26
|
+
|
|
27
|
+
# Time budgets for fallback individual fetching (in seconds).
|
|
27
28
|
AUTOMATION_CONFIG_TIME_BUDGET = _settings.automation_config_time_budget
|
|
28
29
|
SCRIPT_CONFIG_TIME_BUDGET = _settings.script_config_time_budget
|
|
29
30
|
SCENE_CONFIG_TIME_BUDGET = _settings.scene_config_time_budget
|
|
30
31
|
|
|
31
|
-
#
|
|
32
|
-
|
|
32
|
+
# Per-request timeout (seconds) and batch size for the parallel individual
|
|
33
|
+
# config fetches. On HA servers that serve the per-id config endpoint
|
|
34
|
+
# serially, a batch's tail requests queue behind its head and can exceed
|
|
35
|
+
# the per-request timeout despite being perfectly healthy — tune batch
|
|
36
|
+
# size toward 1 and/or raise the timeout on such instances (issue #1784).
|
|
37
|
+
INDIVIDUAL_CONFIG_TIMEOUT = _settings.individual_config_timeout
|
|
38
|
+
INDIVIDUAL_FETCH_BATCH_SIZE = _settings.individual_fetch_batch_size
|
|
33
39
|
|
|
34
40
|
|
|
35
41
|
def _simplify_states_summary(
|
{ha_mcp_dev-7.12.3.dev1859 → ha_mcp_dev-7.12.3.dev1863}/src/ha_mcp/tools/smart_search/_deep.py
RENAMED
|
@@ -18,8 +18,10 @@ from ._config import (
|
|
|
18
18
|
BULK_REST_TIMEOUT,
|
|
19
19
|
DEFAULT_CONCURRENCY_LIMIT,
|
|
20
20
|
INDIVIDUAL_CONFIG_TIMEOUT,
|
|
21
|
+
INDIVIDUAL_FETCH_BATCH_SIZE,
|
|
21
22
|
SCRIPT_CONFIG_TIME_BUDGET,
|
|
22
23
|
)
|
|
24
|
+
from ._fetch import is_timeout_error
|
|
23
25
|
from ._scenes import SceneSearchMixin
|
|
24
26
|
|
|
25
27
|
logger = logging.getLogger(__name__)
|
|
@@ -111,6 +113,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
111
113
|
scene_stats: dict[str, Any] = {
|
|
112
114
|
"failed": 0,
|
|
113
115
|
"skipped": 0,
|
|
116
|
+
"timeout": 0,
|
|
114
117
|
"integration_skipped": 0,
|
|
115
118
|
"registry_failed": False,
|
|
116
119
|
}
|
|
@@ -120,13 +123,16 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
120
123
|
# surface as ``partial: True`` so callers can detect "incomplete
|
|
121
124
|
# zero" / "incomplete partial" results vs a true complete answer.
|
|
122
125
|
# ``_skipped`` = budget exhausted before fetch; ``_failed`` =
|
|
123
|
-
# fetch attempted but raised (caught at ``debug``-level)
|
|
126
|
+
# fetch attempted but raised (caught at ``debug``-level);
|
|
127
|
+
# ``_timeout`` = fetch exceeded the per-request timeout (#1784).
|
|
124
128
|
automation_skipped = 0
|
|
125
129
|
automation_failed = 0
|
|
126
130
|
automation_yaml_skipped = 0
|
|
131
|
+
automation_timeout = 0
|
|
127
132
|
script_skipped = 0
|
|
128
133
|
script_failed = 0
|
|
129
134
|
script_yaml_skipped = 0
|
|
135
|
+
script_timeout = 0
|
|
130
136
|
helper_failed = 0
|
|
131
137
|
dashboard_failed = 0
|
|
132
138
|
|
|
@@ -136,6 +142,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
136
142
|
automation_skipped,
|
|
137
143
|
automation_failed,
|
|
138
144
|
automation_yaml_skipped,
|
|
145
|
+
automation_timeout,
|
|
139
146
|
) = await self._deep_search_automations(
|
|
140
147
|
all_entities,
|
|
141
148
|
automation_unique_id_map,
|
|
@@ -157,6 +164,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
157
164
|
script_skipped,
|
|
158
165
|
script_failed,
|
|
159
166
|
script_yaml_skipped,
|
|
167
|
+
script_timeout,
|
|
160
168
|
) = await self._deep_search_scripts(
|
|
161
169
|
all_entities,
|
|
162
170
|
query_lower,
|
|
@@ -178,6 +186,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
178
186
|
scene_stats["skipped"],
|
|
179
187
|
scene_stats["integration_skipped"],
|
|
180
188
|
scene_stats["registry_failed"],
|
|
189
|
+
scene_stats["timeout"],
|
|
181
190
|
) = await self._deep_search_scenes(
|
|
182
191
|
all_entities,
|
|
183
192
|
query_lower,
|
|
@@ -233,9 +242,11 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
233
242
|
automation_skipped=automation_skipped,
|
|
234
243
|
automation_failed=automation_failed,
|
|
235
244
|
automation_yaml_skipped=automation_yaml_skipped,
|
|
245
|
+
automation_timeout=automation_timeout,
|
|
236
246
|
script_skipped=script_skipped,
|
|
237
247
|
script_failed=script_failed,
|
|
238
248
|
script_yaml_skipped=script_yaml_skipped,
|
|
249
|
+
script_timeout=script_timeout,
|
|
239
250
|
helper_failed=helper_failed,
|
|
240
251
|
dashboard_failed=dashboard_failed,
|
|
241
252
|
)
|
|
@@ -282,22 +293,26 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
282
293
|
exact_match: bool,
|
|
283
294
|
*,
|
|
284
295
|
config_time_budget: float | None = None,
|
|
285
|
-
) -> tuple[list[dict[str, Any]], int, int, int]:
|
|
296
|
+
) -> tuple[list[dict[str, Any]], int, int, int, int]:
|
|
286
297
|
"""Deep-search automations: 3-tier config fetch (REST bulk -> WS bulk -> individual).
|
|
287
298
|
|
|
288
|
-
Returns ``(matches, skipped_count, failed_count, yaml_skipped_count
|
|
289
|
-
``skipped_count`` is non-zero only when bulk fetch
|
|
290
|
-
per-id Attempt-C path AND its wall-clock budget
|
|
291
|
-
configs were fetched; ``failed_count`` is
|
|
292
|
-
config fetches raised a non-404
|
|
293
|
-
``yaml_skipped_count`` is
|
|
294
|
-
returned 404, which is the
|
|
295
|
-
automations (the
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
the
|
|
299
|
+
Returns ``(matches, skipped_count, failed_count, yaml_skipped_count,
|
|
300
|
+
timeout_count)``. ``skipped_count`` is non-zero only when bulk fetch
|
|
301
|
+
fell back to the per-id Attempt-C path AND its wall-clock budget
|
|
302
|
+
exhausted before all configs were fetched; ``failed_count`` is
|
|
303
|
+
non-zero when individual config fetches raised a non-404, non-timeout
|
|
304
|
+
exception (caught at ``debug``-level); ``yaml_skipped_count`` is
|
|
305
|
+
non-zero when individual config fetches returned 404, which is the
|
|
306
|
+
documented HA behaviour for YAML-defined automations (the
|
|
307
|
+
``/config/automation/config/<id>`` REST endpoint only exposes
|
|
308
|
+
UI-storage automations); ``timeout_count`` is non-zero when fetches
|
|
309
|
+
exceeded the per-request ``INDIVIDUAL_CONFIG_TIMEOUT`` — usually a
|
|
310
|
+
sign the HA server serves config reads serially and the concurrent
|
|
311
|
+
batch queued past the timeout, not that anything is broken (#1784).
|
|
312
|
+
All four surface as ``partial: True`` in the response so callers can
|
|
313
|
+
distinguish a complete zero-result from an incomplete one —
|
|
314
|
+
skipped/failed ids carry their score-without-config through the
|
|
315
|
+
merge and fall below the match threshold silently otherwise.
|
|
301
316
|
"""
|
|
302
317
|
automation_entities = [
|
|
303
318
|
e for e in all_entities if e.get("entity_id", "").startswith("automation.")
|
|
@@ -334,6 +349,9 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
334
349
|
|
|
335
350
|
# Attempt C: parallel individual REST calls with time budget (LAST RESORT)
|
|
336
351
|
skipped_count = 0
|
|
352
|
+
failed_count = 0
|
|
353
|
+
yaml_skipped_count = 0
|
|
354
|
+
timeout_count = 0
|
|
337
355
|
if not bulk_fetched:
|
|
338
356
|
uids_to_fetch = [
|
|
339
357
|
uid for _, _, uid, _ in scored if uid and uid not in configs
|
|
@@ -364,7 +382,27 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
364
382
|
f"Automation individual config fetch ({uid}) failed: {e}"
|
|
365
383
|
)
|
|
366
384
|
return (uid, None, "failed")
|
|
385
|
+
except TimeoutError:
|
|
386
|
+
# asyncio.wait_for hit INDIVIDUAL_CONFIG_TIMEOUT. Classify
|
|
387
|
+
# distinctly from "failed": on servers that serialize
|
|
388
|
+
# config reads, a batch's tail requests queue past the
|
|
389
|
+
# timeout while still perfectly healthy (#1784).
|
|
390
|
+
logger.debug(
|
|
391
|
+
f"Automation individual config fetch ({uid}) timed "
|
|
392
|
+
f"out after {INDIVIDUAL_CONFIG_TIMEOUT}s."
|
|
393
|
+
)
|
|
394
|
+
return (uid, None, "timeout")
|
|
367
395
|
except Exception as e:
|
|
396
|
+
if is_timeout_error(e):
|
|
397
|
+
# The REST client's own httpx timeout (HA_TIMEOUT)
|
|
398
|
+
# fired first and arrived wrapped in a
|
|
399
|
+
# HomeAssistantConnectionError — still a timeout,
|
|
400
|
+
# not a failure. See is_timeout_error.
|
|
401
|
+
logger.debug(
|
|
402
|
+
f"Automation individual config fetch ({uid}) "
|
|
403
|
+
f"timed out (client-side HTTP timeout): {e}"
|
|
404
|
+
)
|
|
405
|
+
return (uid, None, "timeout")
|
|
368
406
|
logger.debug(
|
|
369
407
|
f"Automation individual config fetch ({uid}) failed: {e}"
|
|
370
408
|
)
|
|
@@ -375,6 +413,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
375
413
|
failed_count,
|
|
376
414
|
skipped_count,
|
|
377
415
|
yaml_skipped_count,
|
|
416
|
+
timeout_count,
|
|
378
417
|
) = await self._individual_fetch_budgeted(
|
|
379
418
|
uids_to_fetch,
|
|
380
419
|
_fetch_automation_config,
|
|
@@ -385,9 +424,6 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
385
424
|
"automations",
|
|
386
425
|
)
|
|
387
426
|
configs.update(fetched_configs)
|
|
388
|
-
else:
|
|
389
|
-
failed_count = 0
|
|
390
|
-
yaml_skipped_count = 0
|
|
391
427
|
|
|
392
428
|
# Phase 3: Score with whatever configs we have
|
|
393
429
|
matches = [
|
|
@@ -403,7 +439,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
403
439
|
scored, configs, query_lower, exact_match
|
|
404
440
|
)
|
|
405
441
|
]
|
|
406
|
-
return matches, skipped_count, failed_count, yaml_skipped_count
|
|
442
|
+
return matches, skipped_count, failed_count, yaml_skipped_count, timeout_count
|
|
407
443
|
|
|
408
444
|
async def _deep_search_scripts(
|
|
409
445
|
self,
|
|
@@ -412,13 +448,14 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
412
448
|
exact_match: bool,
|
|
413
449
|
*,
|
|
414
450
|
config_time_budget: float | None = None,
|
|
415
|
-
) -> tuple[list[dict[str, Any]], int, int, int]:
|
|
451
|
+
) -> tuple[list[dict[str, Any]], int, int, int, int]:
|
|
416
452
|
"""Deep-search scripts: same 3-tier strategy as automations.
|
|
417
453
|
|
|
418
|
-
Returns ``(matches, skipped_count, failed_count, yaml_skipped_count
|
|
419
|
-
semantics identical to ``_deep_search_automations``
|
|
420
|
-
catches YAML-defined scripts (which
|
|
421
|
-
re-raises as
|
|
454
|
+
Returns ``(matches, skipped_count, failed_count, yaml_skipped_count,
|
|
455
|
+
timeout_count)``; semantics identical to ``_deep_search_automations``
|
|
456
|
+
— the 404 path catches YAML-defined scripts (which
|
|
457
|
+
``client.get_script_config`` re-raises as
|
|
458
|
+
``HomeAssistantAPIError(status_code=404)``).
|
|
422
459
|
"""
|
|
423
460
|
script_entities = [
|
|
424
461
|
e for e in all_entities if e.get("entity_id", "").startswith("script.")
|
|
@@ -451,6 +488,9 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
451
488
|
|
|
452
489
|
# Attempt C: parallel individual fetch with budget (see #879)
|
|
453
490
|
skipped_count = 0
|
|
491
|
+
failed_count = 0
|
|
492
|
+
yaml_skipped_count = 0
|
|
493
|
+
timeout_count = 0
|
|
454
494
|
if not bulk_fetched:
|
|
455
495
|
sids_to_fetch = [
|
|
456
496
|
sid for _, _, sid, _ in scored if sid and sid not in configs
|
|
@@ -478,7 +518,23 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
478
518
|
return (sid, None, "yaml_skipped")
|
|
479
519
|
logger.debug(f"Script individual config fetch ({sid}) failed: {e}")
|
|
480
520
|
return (sid, None, "failed")
|
|
521
|
+
except TimeoutError:
|
|
522
|
+
# See _fetch_automation_config: per-request timeout under
|
|
523
|
+
# batch concurrency, distinct from a real failure (#1784).
|
|
524
|
+
logger.debug(
|
|
525
|
+
f"Script individual config fetch ({sid}) timed out "
|
|
526
|
+
f"after {INDIVIDUAL_CONFIG_TIMEOUT}s."
|
|
527
|
+
)
|
|
528
|
+
return (sid, None, "timeout")
|
|
481
529
|
except Exception as e:
|
|
530
|
+
if is_timeout_error(e):
|
|
531
|
+
# Client-side HTTP timeout arrived wrapped; still a
|
|
532
|
+
# timeout. See _fetch_automation_config.
|
|
533
|
+
logger.debug(
|
|
534
|
+
f"Script individual config fetch ({sid}) timed "
|
|
535
|
+
f"out (client-side HTTP timeout): {e}"
|
|
536
|
+
)
|
|
537
|
+
return (sid, None, "timeout")
|
|
482
538
|
logger.debug(f"Script individual config fetch ({sid}) failed: {e}")
|
|
483
539
|
return (sid, None, "failed")
|
|
484
540
|
|
|
@@ -487,6 +543,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
487
543
|
failed_count,
|
|
488
544
|
skipped_count,
|
|
489
545
|
yaml_skipped_count,
|
|
546
|
+
timeout_count,
|
|
490
547
|
) = await self._individual_fetch_budgeted(
|
|
491
548
|
sids_to_fetch,
|
|
492
549
|
_fetch_script_config,
|
|
@@ -497,9 +554,6 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
497
554
|
"scripts",
|
|
498
555
|
)
|
|
499
556
|
configs.update(fetched_configs)
|
|
500
|
-
else:
|
|
501
|
-
failed_count = 0
|
|
502
|
-
yaml_skipped_count = 0
|
|
503
557
|
|
|
504
558
|
# Phase 3: Score scripts
|
|
505
559
|
matches = [
|
|
@@ -516,7 +570,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
516
570
|
scored, configs, query_lower, exact_match
|
|
517
571
|
)
|
|
518
572
|
]
|
|
519
|
-
return matches, skipped_count, failed_count, yaml_skipped_count
|
|
573
|
+
return matches, skipped_count, failed_count, yaml_skipped_count, timeout_count
|
|
520
574
|
|
|
521
575
|
async def _search_helper_type(
|
|
522
576
|
self,
|
|
@@ -796,6 +850,8 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
796
850
|
script_failed: int = 0,
|
|
797
851
|
automation_yaml_skipped: int = 0,
|
|
798
852
|
script_yaml_skipped: int = 0,
|
|
853
|
+
automation_timeout: int = 0,
|
|
854
|
+
script_timeout: int = 0,
|
|
799
855
|
helper_failed: int = 0,
|
|
800
856
|
dashboard_failed: int = 0,
|
|
801
857
|
) -> dict[str, Any]:
|
|
@@ -856,6 +912,8 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
856
912
|
script_failed=script_failed,
|
|
857
913
|
automation_yaml_skipped=automation_yaml_skipped,
|
|
858
914
|
script_yaml_skipped=script_yaml_skipped,
|
|
915
|
+
automation_timeout=automation_timeout,
|
|
916
|
+
script_timeout=script_timeout,
|
|
859
917
|
helper_failed=helper_failed,
|
|
860
918
|
dashboard_failed=dashboard_failed,
|
|
861
919
|
)
|
|
@@ -871,18 +929,25 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
871
929
|
script_failed: int = 0,
|
|
872
930
|
automation_yaml_skipped: int = 0,
|
|
873
931
|
script_yaml_skipped: int = 0,
|
|
932
|
+
automation_timeout: int = 0,
|
|
933
|
+
script_timeout: int = 0,
|
|
874
934
|
helper_failed: int = 0,
|
|
875
935
|
dashboard_failed: int = 0,
|
|
876
936
|
) -> None:
|
|
877
937
|
"""Set ``partial: True`` when the deep-search per-type fetch path lost
|
|
878
938
|
data — either the Attempt-C wall-clock budget exhausted
|
|
879
|
-
(``*_skipped``), individual fetches raised non-404
|
|
880
|
-
(``*_failed``, caught at ``debug``-level so they would
|
|
881
|
-
be silent),
|
|
882
|
-
is YAML-defined (``*_yaml_skipped``)
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
939
|
+
(``*_skipped``), individual fetches raised non-404 non-timeout
|
|
940
|
+
exceptions (``*_failed``, caught at ``debug``-level so they would
|
|
941
|
+
otherwise be silent), individual fetches returned 404 because the
|
|
942
|
+
entity is YAML-defined (``*_yaml_skipped``), or individual fetches
|
|
943
|
+
exceeded the per-request timeout (``*_timeout`` — on servers that
|
|
944
|
+
serialize config reads a concurrent batch's tail queues past the
|
|
945
|
+
timeout while perfectly healthy, so the wording points at the
|
|
946
|
+
batch-size/timeout knobs rather than at the entities, #1784).
|
|
947
|
+
``helper_failed`` / ``dashboard_failed`` cover the helper- and
|
|
948
|
+
dashboard-list/config backends, whose per-unit ``except`` blocks
|
|
949
|
+
would otherwise swallow a backend outage to an empty list with no
|
|
950
|
+
signal.
|
|
886
951
|
|
|
887
952
|
Mirrors ``_apply_scene_partial_flag`` 's "looks complete when it
|
|
888
953
|
isn't" coverage onto the automation/script/helper/dashboard paths —
|
|
@@ -902,52 +967,64 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
902
967
|
and the new reasons are concatenated with ``" ; "``.
|
|
903
968
|
"""
|
|
904
969
|
reasons: list[str] = []
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
"
|
|
911
|
-
"
|
|
912
|
-
"
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
970
|
+
# The automation and script fragments are symmetric (noun, per-id
|
|
971
|
+
# endpoint, budget env var); loop rather than duplicating the four
|
|
972
|
+
# per-class fragments per type.
|
|
973
|
+
for noun, endpoint, budget_env, skipped, failed, yaml_skipped, timeout in (
|
|
974
|
+
(
|
|
975
|
+
"automation",
|
|
976
|
+
"/config/automation/config",
|
|
977
|
+
"HAMCP_AUTOMATION_CONFIG_TIME_BUDGET",
|
|
978
|
+
automation_skipped,
|
|
979
|
+
automation_failed,
|
|
980
|
+
automation_yaml_skipped,
|
|
981
|
+
automation_timeout,
|
|
982
|
+
),
|
|
983
|
+
(
|
|
984
|
+
"script",
|
|
985
|
+
"/config/script/config",
|
|
986
|
+
"HAMCP_SCRIPT_CONFIG_TIME_BUDGET",
|
|
987
|
+
script_skipped,
|
|
988
|
+
script_failed,
|
|
989
|
+
script_yaml_skipped,
|
|
990
|
+
script_timeout,
|
|
991
|
+
),
|
|
992
|
+
):
|
|
993
|
+
if skipped:
|
|
994
|
+
reasons.append(
|
|
995
|
+
f"{skipped} {noun}(s) not scanned (time budget "
|
|
996
|
+
"exhausted) — their match status is unknown; this result "
|
|
997
|
+
"is not exhaustive. Pass `config_time_budget=` on "
|
|
998
|
+
"`ha_search` to raise the per-call limit (or, for the "
|
|
999
|
+
f"default, set {budget_env} or the matching field in "
|
|
1000
|
+
"the web Settings UI's Advanced section)."
|
|
1001
|
+
)
|
|
1002
|
+
if failed:
|
|
1003
|
+
reasons.append(
|
|
1004
|
+
f"{failed} {noun}(s) not scanned (per-id fetch raised "
|
|
1005
|
+
"a non-404 error) — their match status is unknown; this "
|
|
1006
|
+
"result is not exhaustive."
|
|
1007
|
+
)
|
|
1008
|
+
if yaml_skipped:
|
|
1009
|
+
reasons.append(
|
|
1010
|
+
f"{yaml_skipped} {noun}(s) not scanned (per-id config "
|
|
1011
|
+
"endpoint returned 404 — these are likely YAML-defined "
|
|
1012
|
+
f"{noun}s that the {endpoint} REST endpoint does not "
|
|
1013
|
+
"expose) — their match status is unknown; this result "
|
|
1014
|
+
"is not exhaustive."
|
|
1015
|
+
)
|
|
1016
|
+
if timeout:
|
|
1017
|
+
reasons.append(
|
|
1018
|
+
f"{timeout} {noun}(s) not scanned (per-id fetch timed "
|
|
1019
|
+
f"out after {INDIVIDUAL_CONFIG_TIMEOUT}s while "
|
|
1020
|
+
f"{INDIVIDUAL_FETCH_BATCH_SIZE} fetches ran concurrently "
|
|
1021
|
+
"— this usually means the HA server serves config reads "
|
|
1022
|
+
f"serially, not that the {noun}s are broken) — their "
|
|
1023
|
+
"match status is unknown; this result is not exhaustive. "
|
|
1024
|
+
"Lower HAMCP_INDIVIDUAL_FETCH_BATCH_SIZE and/or raise "
|
|
1025
|
+
"HAMCP_INDIVIDUAL_CONFIG_TIMEOUT (or the matching fields "
|
|
1026
|
+
"in the web Settings UI's Advanced section)."
|
|
1027
|
+
)
|
|
951
1028
|
if helper_failed:
|
|
952
1029
|
reasons.append(
|
|
953
1030
|
f"{helper_failed} helper backend(s) not scanned (per-type list, "
|