ha-mcp-dev 8.3.0.dev2465__tar.gz → 8.3.0.dev2467__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-8.3.0.dev2465/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.3.0.dev2467}/PKG-INFO +1 -1
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/pyproject.toml +2 -1
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/client/websocket_client.py +44 -5
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/auto_backup.py +65 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_deep.py +6 -1
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_scenes.py +156 -47
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_filesystem.py +90 -23
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_search.py +9 -1
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/MANIFEST.in +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/README.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/setup.cfg +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_rich_compat.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/requirements.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/version.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/auth/oidc_compat.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/hacs_auto_refresh.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/log_filters.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/editing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/redaction.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/renamed_tools.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/area-target-over-entity-list/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/arrive-home-automation/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/assist-shortcut-card/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/avg-temperature/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/backup-deletion-confirmation/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/blueprint-authoring/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/button-event-entity/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/device-id-trigger-migration/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/helper-flow-fields-not-platform-yaml/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/media-player-song/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/motion-light-automation-mode/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/rename-entity-impact-analysis/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/template-attributes-yaml-exception/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/timer-finished-automation/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/evals/vacuum-last-clean/case.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/scripts/check_eval_cases.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/backups.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_locale_policy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/en.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/es.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/it.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/ko.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/tlh.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/best_practice_result.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/best_practice_variables.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/bulk_selector.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/component_config_entries.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/config_entry_identity.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/error_log_parsing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/hacs_registration.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/log_common.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/log_sources.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/log_sources_supervisor.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_graph.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_logs.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_security_policy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/entity_membership.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/visibility/enforcement.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/tests/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/tests/test_constants.py +0 -0
- {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/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 = "8.3.0.
|
|
7
|
+
version = "8.3.0.dev2467"
|
|
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
|
|
@@ -306,6 +306,7 @@ dev = [
|
|
|
306
306
|
"ruff>=0.12.12",
|
|
307
307
|
"testcontainers>=4.13.0",
|
|
308
308
|
"ast-grep-cli>=0.42.0",
|
|
309
|
+
"pyyaml>=6.0",
|
|
309
310
|
]
|
|
310
311
|
|
|
311
312
|
# Semantic versioning configuration
|
|
@@ -111,10 +111,15 @@ class WebSocketConnectionState:
|
|
|
111
111
|
return self._pending_requests.pop(message_id, None)
|
|
112
112
|
|
|
113
113
|
def cancel_pending_request(self, message_id: int) -> None:
|
|
114
|
-
"""
|
|
114
|
+
"""Remove a pending request future, cancelling it or draining its exception."""
|
|
115
115
|
future = self._pending_requests.pop(message_id, None)
|
|
116
116
|
if future and not future.done():
|
|
117
117
|
future.cancel()
|
|
118
|
+
elif future and not future.cancelled():
|
|
119
|
+
# A done future may hold the exception reset_connection set on it;
|
|
120
|
+
# retrieve it here so dropping the last reference doesn't log an
|
|
121
|
+
# ERROR-level "Future exception was never retrieved" at GC.
|
|
122
|
+
future.exception()
|
|
118
123
|
|
|
119
124
|
def register_event_response(
|
|
120
125
|
self, message_id: int
|
|
@@ -133,10 +138,13 @@ class WebSocketConnectionState:
|
|
|
133
138
|
return self._event_responses.pop(message_id, None)
|
|
134
139
|
|
|
135
140
|
def cancel_event_response(self, message_id: int) -> None:
|
|
136
|
-
"""
|
|
141
|
+
"""Remove a pending event future, cancelling it or draining its exception."""
|
|
137
142
|
future = self._event_responses.pop(message_id, None)
|
|
138
143
|
if future and not future.done():
|
|
139
144
|
future.cancel()
|
|
145
|
+
elif future and not future.cancelled():
|
|
146
|
+
# Same GC guard as cancel_pending_request above.
|
|
147
|
+
future.exception()
|
|
140
148
|
|
|
141
149
|
def store_auth_message(self, message_type: str, data: dict[str, Any]) -> None:
|
|
142
150
|
"""Store an authentication handshake message."""
|
|
@@ -757,6 +765,12 @@ class HomeAssistantWebSocketClient:
|
|
|
757
765
|
self.cancel_pending_response(message_id)
|
|
758
766
|
self.cancel_event_response(message_id)
|
|
759
767
|
raise
|
|
768
|
+
except BaseException:
|
|
769
|
+
# Cancellation mid-send: skips the clause above and would leave BOTH
|
|
770
|
+
# registrations behind — drop them before propagating.
|
|
771
|
+
self.cancel_pending_response(message_id)
|
|
772
|
+
self.cancel_event_response(message_id)
|
|
773
|
+
raise
|
|
760
774
|
|
|
761
775
|
try:
|
|
762
776
|
result_response = await asyncio.wait_for(
|
|
@@ -780,7 +794,12 @@ class HomeAssistantWebSocketClient:
|
|
|
780
794
|
|
|
781
795
|
try:
|
|
782
796
|
event_response = await asyncio.wait_for(event_future, timeout=wait_timeout)
|
|
783
|
-
except
|
|
797
|
+
except BaseException:
|
|
798
|
+
# A cancelled caller leaves the event registration in
|
|
799
|
+
# _event_responses until an event with this id arrives, which
|
|
800
|
+
# nothing sends once the caller is gone. The cleanup is
|
|
801
|
+
# exception-type-independent and the original exception re-raises
|
|
802
|
+
# unchanged.
|
|
784
803
|
self.cancel_event_response(message_id)
|
|
785
804
|
raise
|
|
786
805
|
|
|
@@ -832,10 +851,19 @@ class HomeAssistantWebSocketClient:
|
|
|
832
851
|
except Exception:
|
|
833
852
|
self.cancel_pending_response(message_id)
|
|
834
853
|
raise
|
|
854
|
+
except BaseException:
|
|
855
|
+
# Cancellation mid-send: skips the clause above and would leave the
|
|
856
|
+
# pending future registered — drop it before propagating.
|
|
857
|
+
self.cancel_pending_response(message_id)
|
|
858
|
+
raise
|
|
835
859
|
|
|
836
860
|
try:
|
|
837
861
|
response = await asyncio.wait_for(future, timeout=30.0)
|
|
838
|
-
except
|
|
862
|
+
except BaseException:
|
|
863
|
+
# A cancelled caller leaks the pending future, which nothing ever
|
|
864
|
+
# resolves once the caller is gone. The cleanup is
|
|
865
|
+
# exception-type-independent and the original exception re-raises
|
|
866
|
+
# unchanged.
|
|
839
867
|
self.cancel_pending_response(message_id)
|
|
840
868
|
raise
|
|
841
869
|
|
|
@@ -927,10 +955,21 @@ class HomeAssistantWebSocketClient:
|
|
|
927
955
|
self._state.unregister_subscription_queue(message_id)
|
|
928
956
|
self.cancel_pending_response(message_id)
|
|
929
957
|
raise
|
|
958
|
+
except BaseException:
|
|
959
|
+
# Cancellation mid-send: skips the clause above and would leave BOTH
|
|
960
|
+
# the queue and the pending future registered — the queue never
|
|
961
|
+
# drains and buffers every later event for this id forever.
|
|
962
|
+
self._state.unregister_subscription_queue(message_id)
|
|
963
|
+
self.cancel_pending_response(message_id)
|
|
964
|
+
raise
|
|
930
965
|
|
|
931
966
|
try:
|
|
932
967
|
response = await asyncio.wait_for(result_future, timeout=timeout)
|
|
933
|
-
except
|
|
968
|
+
except BaseException:
|
|
969
|
+
# A cancelled caller leaks the pending future AND the subscription
|
|
970
|
+
# queue, which keeps accumulating events with no reader. The
|
|
971
|
+
# cleanup is exception-type-independent and the original exception
|
|
972
|
+
# re-raises unchanged.
|
|
934
973
|
self._state.unregister_subscription_queue(message_id)
|
|
935
974
|
self.cancel_pending_response(message_id)
|
|
936
975
|
raise
|
|
@@ -32,6 +32,8 @@ import logging
|
|
|
32
32
|
from collections.abc import Awaitable, Callable
|
|
33
33
|
from typing import Any
|
|
34
34
|
|
|
35
|
+
from fastmcp.exceptions import ToolError
|
|
36
|
+
|
|
35
37
|
from ..backup_manager import (
|
|
36
38
|
_CAPTURE_TRANSIENT_ERRORS,
|
|
37
39
|
MandatoryBackupError,
|
|
@@ -51,6 +53,48 @@ logger = logging.getLogger(__name__)
|
|
|
51
53
|
_DECORATOR_TRANSIENT_ERRORS = _CAPTURE_TRANSIENT_ERRORS
|
|
52
54
|
|
|
53
55
|
|
|
56
|
+
def _component_not_installed_cause(err: BaseException) -> ToolError | None:
|
|
57
|
+
"""Return the ``COMPONENT_NOT_INSTALLED`` ToolError behind a capture failure.
|
|
58
|
+
|
|
59
|
+
A mandatory pre-write snapshot reads the current content through the same
|
|
60
|
+
File & YAML Tools services the wrapped write needs, so on an install
|
|
61
|
+
without that config entry the capture fails FIRST and the write would be
|
|
62
|
+
refused as ``BACKUP_CAPTURE_FAILED`` — burying the actionable "add the
|
|
63
|
+
entry" guidance one level deep (#2292). Walk the cause chain for the
|
|
64
|
+
original structured error so the caller can surface it unchanged: retrying
|
|
65
|
+
the backup cannot succeed, and the remediation is the component's, not the
|
|
66
|
+
backup pipeline's.
|
|
67
|
+
"""
|
|
68
|
+
seen: set[int] = set()
|
|
69
|
+
# Worklist over BOTH chain links: an explicit ``raise ... from other``
|
|
70
|
+
# sets an unrelated ``__cause__`` while the component error stays in
|
|
71
|
+
# ``__context__`` — following only one link per node would miss it.
|
|
72
|
+
stack: list[BaseException] = [
|
|
73
|
+
link for link in (err.__cause__, err.__context__) if link is not None
|
|
74
|
+
]
|
|
75
|
+
while stack:
|
|
76
|
+
cause = stack.pop()
|
|
77
|
+
if id(cause) in seen:
|
|
78
|
+
continue
|
|
79
|
+
seen.add(id(cause))
|
|
80
|
+
if isinstance(cause, ToolError):
|
|
81
|
+
try:
|
|
82
|
+
payload = json.loads(str(cause))
|
|
83
|
+
except (TypeError, ValueError):
|
|
84
|
+
payload = None
|
|
85
|
+
if (
|
|
86
|
+
isinstance(payload, dict)
|
|
87
|
+
and isinstance(payload.get("error"), dict)
|
|
88
|
+
and payload["error"].get("code")
|
|
89
|
+
== ErrorCode.COMPONENT_NOT_INSTALLED.value
|
|
90
|
+
):
|
|
91
|
+
return cause
|
|
92
|
+
stack.extend(
|
|
93
|
+
link for link in (cause.__cause__, cause.__context__) if link is not None
|
|
94
|
+
)
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
54
98
|
def _resolve_str(value: Any) -> str:
|
|
55
99
|
"""Stringify a kwarg value defensively. ``None`` → empty string."""
|
|
56
100
|
if value is None:
|
|
@@ -288,6 +332,27 @@ async def _capture_pre_write_snapshot(
|
|
|
288
332
|
# wrapped write never runs, so nothing has been changed.
|
|
289
333
|
# The ToolError this raises propagates rather than being
|
|
290
334
|
# swallowed by the best-effort handler below.
|
|
335
|
+
component_error = _component_not_installed_cause(err)
|
|
336
|
+
if component_error is not None:
|
|
337
|
+
# The capture failed because the File & YAML Tools entry is
|
|
338
|
+
# not set up — the same reason the wrapped write itself would
|
|
339
|
+
# refuse. Surface that actionable error (add the entry), not a
|
|
340
|
+
# backup complaint (#2292). The write is still blocked. ``from
|
|
341
|
+
# None`` because this IS the underlying error being unwrapped;
|
|
342
|
+
# chaining it back onto the wrapper it came out of would only
|
|
343
|
+
# print the chain twice (and B904 wants the intent explicit).
|
|
344
|
+
# ``from None`` also drops the wrapper's own text from the
|
|
345
|
+
# traceback, so record it here or the only account of WHICH
|
|
346
|
+
# mandatory capture was re-pointed at the component error is
|
|
347
|
+
# gone.
|
|
348
|
+
logger.warning(
|
|
349
|
+
"Auto-backup: mandatory capture for '%s' failed (%s) — "
|
|
350
|
+
"surfacing the underlying component error instead; the "
|
|
351
|
+
"write is blocked either way",
|
|
352
|
+
func.__name__,
|
|
353
|
+
err,
|
|
354
|
+
)
|
|
355
|
+
raise component_error from None
|
|
291
356
|
raise_tool_error(
|
|
292
357
|
create_error_response(
|
|
293
358
|
ErrorCode.BACKUP_CAPTURE_FAILED,
|
{ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_deep.py
RENAMED
|
@@ -238,6 +238,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
238
238
|
# search is not requested.
|
|
239
239
|
scene_stats: dict[str, Any] = {
|
|
240
240
|
"failed": 0,
|
|
241
|
+
"yaml_skipped": 0,
|
|
241
242
|
"skipped": 0,
|
|
242
243
|
"timeout": 0,
|
|
243
244
|
"integration_skipped": 0,
|
|
@@ -316,6 +317,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
316
317
|
(
|
|
317
318
|
results["scenes"],
|
|
318
319
|
scene_stats["failed"],
|
|
320
|
+
scene_stats["yaml_skipped"],
|
|
319
321
|
scene_stats["skipped"],
|
|
320
322
|
scene_stats["integration_skipped"],
|
|
321
323
|
scene_stats["registry_failed"],
|
|
@@ -1327,6 +1329,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
1327
1329
|
scene_body_incomplete=bool(
|
|
1328
1330
|
scene_stats.get("skipped")
|
|
1329
1331
|
or scene_stats.get("failed")
|
|
1332
|
+
or scene_stats.get("yaml_skipped")
|
|
1330
1333
|
or scene_stats.get("timeout")
|
|
1331
1334
|
),
|
|
1332
1335
|
automation_skipped=automation_skipped,
|
|
@@ -1492,7 +1495,9 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
1492
1495
|
"endpoint returned 404 — these are likely YAML-defined "
|
|
1493
1496
|
f"{noun}s that the {endpoint} REST endpoint does not "
|
|
1494
1497
|
"expose) — their match status is unknown; this result "
|
|
1495
|
-
"is not exhaustive."
|
|
1498
|
+
"is not exhaustive. Their definitions live outside HA "
|
|
1499
|
+
f"storage (typically {noun}s.yaml); check there if the "
|
|
1500
|
+
"match matters."
|
|
1496
1501
|
)
|
|
1497
1502
|
if timeout:
|
|
1498
1503
|
reasons.append(
|
{ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_scenes.py
RENAMED
|
@@ -4,6 +4,11 @@ import asyncio
|
|
|
4
4
|
import logging
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
+
from ...client.rest_client import (
|
|
8
|
+
HomeAssistantAPIError,
|
|
9
|
+
SceneResolution,
|
|
10
|
+
SceneStorageConfigNotFoundError,
|
|
11
|
+
)
|
|
7
12
|
from ._config import (
|
|
8
13
|
ENTITY_REGISTRY_TIMEOUT,
|
|
9
14
|
INDIVIDUAL_CONFIG_TIMEOUT,
|
|
@@ -212,6 +217,112 @@ class SceneSearchMixin(ConfigFetchMixin):
|
|
|
212
217
|
)
|
|
213
218
|
return scene_id
|
|
214
219
|
|
|
220
|
+
async def _fetch_one_scene_config(
|
|
221
|
+
self,
|
|
222
|
+
sid: str,
|
|
223
|
+
slug_to_storage_id: dict[str, str],
|
|
224
|
+
failed_errors: list[str],
|
|
225
|
+
*,
|
|
226
|
+
registry_failed: bool,
|
|
227
|
+
) -> tuple[str, dict[str, Any] | None, str | None]:
|
|
228
|
+
"""Fetch one scene's storage config and classify the outcome.
|
|
229
|
+
|
|
230
|
+
Extracted from ``_deep_search_scenes``'s Attempt-C fetch closure
|
|
231
|
+
(C901). Returns ``(sid, config-or-None, marker)`` where the marker is
|
|
232
|
+
the ``_individual_fetch_budgeted`` classification: ``None`` (success),
|
|
233
|
+
``"yaml_skipped"``, ``"timeout"`` or ``"failed"``.
|
|
234
|
+
"""
|
|
235
|
+
# Hand the client the resolution this scan already made instead of the
|
|
236
|
+
# bare storage key. The client's own resolver treats its input as an
|
|
237
|
+
# entity-id slug ("scene.<input>" registry/state lookups), so a bare
|
|
238
|
+
# storage key that differs from the name-derived slug misses BOTH
|
|
239
|
+
# lookups and every not-a-storage-scene 404 degrades to the bare
|
|
240
|
+
# "vanished" case — round-6 of #2302's no-component lane caught exactly
|
|
241
|
+
# that on the seeded YAML-with-id scene. Passing the resolution keeps
|
|
242
|
+
# the 404 classification working AND drops the per-scene registry
|
|
243
|
+
# round-trip the internal re-resolve used to pay.
|
|
244
|
+
#
|
|
245
|
+
# ONLY when the registry walk succeeded, though: on the failed walk the
|
|
246
|
+
# map is empty, and a synthesized (sid, registry_hit=False) resolution
|
|
247
|
+
# would suppress the client's own per-scene resolver — which may work
|
|
248
|
+
# again by fetch time on a transient list failure, and is the only way
|
|
249
|
+
# a UI-renamed storage scene (storage key != slug) can still resolve.
|
|
250
|
+
# Leaving resolution unset there restores the pre-#2302 per-scene
|
|
251
|
+
# resolve for exactly that path (Codex review on #2302).
|
|
252
|
+
resolution = (
|
|
253
|
+
None
|
|
254
|
+
if registry_failed
|
|
255
|
+
else SceneResolution(
|
|
256
|
+
storage_key=slug_to_storage_id.get(sid, sid),
|
|
257
|
+
registry_hit=sid in slug_to_storage_id,
|
|
258
|
+
platform=None,
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
try:
|
|
262
|
+
config_resp = await asyncio.wait_for(
|
|
263
|
+
# Fetch by STORAGE key (what the endpoint indexes on),
|
|
264
|
+
# return under the SLUG (what the scoring pass looks up).
|
|
265
|
+
self.client.get_scene_config(sid, resolution=resolution),
|
|
266
|
+
timeout=INDIVIDUAL_CONFIG_TIMEOUT,
|
|
267
|
+
)
|
|
268
|
+
return (sid, config_resp.get("config", {}), None)
|
|
269
|
+
except SceneStorageConfigNotFoundError as e:
|
|
270
|
+
# The entity EXISTS, it is just not an editable storage scene:
|
|
271
|
+
# a YAML-defined scene carrying an ``id:`` (which registers
|
|
272
|
+
# under platform ``homeassistant`` exactly like a storage
|
|
273
|
+
# scene, so the registry walk cannot pre-filter it), an
|
|
274
|
+
# ``id``-less YAML scene the client confirmed through the state
|
|
275
|
+
# machine, or an integration-managed (Hue/deCONZ/...) scene.
|
|
276
|
+
# The client decides that classification itself
|
|
277
|
+
# (``_raise_scene_config_404``) from the registry/state lookups
|
|
278
|
+
# it makes for the fetch, independently of the search-side
|
|
279
|
+
# registry filter — which is why this branch stays correct on
|
|
280
|
+
# the registry-failed attempt-all path, where integration-
|
|
281
|
+
# managed scenes DO reach the fetch. Every one of them is a
|
|
282
|
+
# structural gap rather than a fetch outage, so classify it the
|
|
283
|
+
# way the automation/script fetchers do (#2292).
|
|
284
|
+
logger.debug(
|
|
285
|
+
f"Scene individual config fetch ({sid}) returned 404 "
|
|
286
|
+
f"— not a storage scene: {e}"
|
|
287
|
+
)
|
|
288
|
+
return (sid, None, "yaml_skipped")
|
|
289
|
+
except HomeAssistantAPIError as e:
|
|
290
|
+
if e.status_code == 404:
|
|
291
|
+
# A bare 404 (not the subclass above) means the entity is in
|
|
292
|
+
# neither the registry nor the state machine: it vanished
|
|
293
|
+
# between get_states() and this fetch. That IS a real gap in
|
|
294
|
+
# this result, so it belongs in the failed bucket with a
|
|
295
|
+
# representative sample.
|
|
296
|
+
logger.debug(
|
|
297
|
+
f"Scene individual config fetch ({sid}) returned 404 "
|
|
298
|
+
"— entity no longer exists since enumeration."
|
|
299
|
+
)
|
|
300
|
+
else:
|
|
301
|
+
logger.debug(f"Scene individual config fetch ({sid}) failed: {e}")
|
|
302
|
+
record_first_failure(failed_errors, e)
|
|
303
|
+
return (sid, None, "failed")
|
|
304
|
+
except TimeoutError:
|
|
305
|
+
# Per-request timeout under batch concurrency — distinct
|
|
306
|
+
# from a real failure; see _fetch_automation_config
|
|
307
|
+
# (#1784).
|
|
308
|
+
logger.debug(
|
|
309
|
+
f"Scene individual config fetch ({sid}) timed out "
|
|
310
|
+
f"after {INDIVIDUAL_CONFIG_TIMEOUT}s."
|
|
311
|
+
)
|
|
312
|
+
return (sid, None, "timeout")
|
|
313
|
+
except Exception as e:
|
|
314
|
+
if is_timeout_error(e):
|
|
315
|
+
# Client-side HTTP timeout arrived wrapped; still a
|
|
316
|
+
# timeout. See is_timeout_error in _fetch.
|
|
317
|
+
logger.debug(
|
|
318
|
+
f"Scene individual config fetch ({sid}) timed "
|
|
319
|
+
f"out (client-side HTTP timeout): {e}"
|
|
320
|
+
)
|
|
321
|
+
return (sid, None, "timeout")
|
|
322
|
+
logger.debug(f"Scene individual config fetch ({sid}) failed: {e}")
|
|
323
|
+
record_first_failure(failed_errors, e)
|
|
324
|
+
return (sid, None, "failed")
|
|
325
|
+
|
|
215
326
|
async def _deep_search_scenes(
|
|
216
327
|
self,
|
|
217
328
|
all_entities: list[dict[str, Any]],
|
|
@@ -222,19 +333,19 @@ class SceneSearchMixin(ConfigFetchMixin):
|
|
|
222
333
|
prefetched_registry: Any = None,
|
|
223
334
|
storage_ids_out: dict[str, str] | None = None,
|
|
224
335
|
graph_entity_ids: set[str] | None = None,
|
|
225
|
-
) -> tuple[list[dict[str, Any]], int, int, int, bool, int, str | None]:
|
|
336
|
+
) -> tuple[list[dict[str, Any]], int, int, int, int, bool, int, str | None]:
|
|
226
337
|
"""Deep-search scenes: per-id fetch plus registry-walk augmentation.
|
|
227
338
|
|
|
228
339
|
Scenes have no listing primitive, so entities are enumerated from
|
|
229
340
|
get_states() and configs fetched per id. Returns the scene results plus
|
|
230
|
-
the
|
|
341
|
+
the seven diagnostic signals feeding the response ``partial`` /
|
|
231
342
|
``partial_reason``:
|
|
232
|
-
``(results, failed_count,
|
|
233
|
-
registry_failed, timeout_count, failed_sample)``.
|
|
234
|
-
is one representative ``summarize_fetch_error``
|
|
235
|
-
``failed``-class exception (``None`` when none occurred)
|
|
236
|
-
automation/script mirror in ``_deep_search_automations``
|
|
237
|
-
follow-up).
|
|
343
|
+
``(results, failed_count, yaml_skipped_count, skipped_count,
|
|
344
|
+
integration_skipped, registry_failed, timeout_count, failed_sample)``.
|
|
345
|
+
``failed_sample`` is one representative ``summarize_fetch_error``
|
|
346
|
+
summary of a ``failed``-class exception (``None`` when none occurred)
|
|
347
|
+
— see the automation/script mirror in ``_deep_search_automations``
|
|
348
|
+
(#1784 follow-up).
|
|
238
349
|
"""
|
|
239
350
|
scene_entities = [
|
|
240
351
|
e for e in all_entities if e.get("entity_id", "").startswith("scene.")
|
|
@@ -263,13 +374,14 @@ class SceneSearchMixin(ConfigFetchMixin):
|
|
|
263
374
|
) = await self._walk_scene_registry(prefetched_registry=prefetched_registry)
|
|
264
375
|
# Hand the slug-to-storage-key map back to deep_search, which needs it
|
|
265
376
|
# to give reference-graph scene hits their real storage key. An out
|
|
266
|
-
# parameter rather than
|
|
377
|
+
# parameter rather than a ninth return value: the one production
|
|
267
378
|
# caller and the one test that drive this function unpack its tuple
|
|
268
379
|
# positionally.
|
|
269
380
|
if storage_ids_out is not None:
|
|
270
381
|
storage_ids_out.update(slug_to_storage_id)
|
|
271
382
|
|
|
272
383
|
failed_count = 0
|
|
384
|
+
yaml_skipped_count = 0
|
|
273
385
|
skipped_count = 0
|
|
274
386
|
integration_skipped = 0
|
|
275
387
|
timeout_count = 0
|
|
@@ -290,45 +402,15 @@ class SceneSearchMixin(ConfigFetchMixin):
|
|
|
290
402
|
async def _fetch_scene_config(
|
|
291
403
|
sid: str,
|
|
292
404
|
) -> tuple[str, dict[str, Any] | None, str | None]:
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
# return under the SLUG (what the scoring pass looks up).
|
|
297
|
-
self.client.get_scene_config(slug_to_storage_id.get(sid, sid)),
|
|
298
|
-
timeout=INDIVIDUAL_CONFIG_TIMEOUT,
|
|
299
|
-
)
|
|
300
|
-
return (sid, config_resp.get("config", {}), None)
|
|
301
|
-
except TimeoutError:
|
|
302
|
-
# Per-request timeout under batch concurrency — distinct
|
|
303
|
-
# from a real failure; see _fetch_automation_config
|
|
304
|
-
# (#1784).
|
|
305
|
-
logger.debug(
|
|
306
|
-
f"Scene individual config fetch ({sid}) timed out "
|
|
307
|
-
f"after {INDIVIDUAL_CONFIG_TIMEOUT}s."
|
|
308
|
-
)
|
|
309
|
-
return (sid, None, "timeout")
|
|
310
|
-
except Exception as e:
|
|
311
|
-
if is_timeout_error(e):
|
|
312
|
-
# Client-side HTTP timeout arrived wrapped; still a
|
|
313
|
-
# timeout. See is_timeout_error in _fetch.
|
|
314
|
-
logger.debug(
|
|
315
|
-
f"Scene individual config fetch ({sid}) timed "
|
|
316
|
-
f"out (client-side HTTP timeout): {e}"
|
|
317
|
-
)
|
|
318
|
-
return (sid, None, "timeout")
|
|
319
|
-
logger.debug(f"Scene individual config fetch ({sid}) failed: {e}")
|
|
320
|
-
record_first_failure(failed_errors, e)
|
|
321
|
-
return (sid, None, "failed")
|
|
405
|
+
return await self._fetch_one_scene_config(
|
|
406
|
+
sid, slug_to_storage_id, failed_errors, registry_failed=registry_failed
|
|
407
|
+
)
|
|
322
408
|
|
|
323
409
|
(
|
|
324
410
|
fetched_configs,
|
|
325
411
|
failed_count,
|
|
326
412
|
skipped_count,
|
|
327
|
-
|
|
328
|
-
# upstream via `_walk_scene_registry`; the 4th tuple slot
|
|
329
|
-
# from `_individual_fetch_budgeted` is therefore expected
|
|
330
|
-
# to stay at zero on this path.
|
|
331
|
-
_scene_yaml_skipped,
|
|
413
|
+
yaml_skipped_count,
|
|
332
414
|
timeout_count,
|
|
333
415
|
) = await self._individual_fetch_budgeted(
|
|
334
416
|
sids_to_fetch,
|
|
@@ -365,6 +447,7 @@ class SceneSearchMixin(ConfigFetchMixin):
|
|
|
365
447
|
return (
|
|
366
448
|
scene_results,
|
|
367
449
|
failed_count,
|
|
450
|
+
yaml_skipped_count,
|
|
368
451
|
skipped_count,
|
|
369
452
|
integration_skipped,
|
|
370
453
|
registry_failed,
|
|
@@ -392,9 +475,11 @@ class SceneSearchMixin(ConfigFetchMixin):
|
|
|
392
475
|
failed = scene_stats["failed"]
|
|
393
476
|
skipped = scene_stats["skipped"]
|
|
394
477
|
# .get(): tolerate older callers/tests that build the stats dict
|
|
395
|
-
# without the timeout key (added for #1784)
|
|
478
|
+
# without the timeout key (added for #1784) or without the
|
|
479
|
+
# yaml_skipped key (added for #2292).
|
|
396
480
|
timeout = scene_stats.get("timeout", 0)
|
|
397
|
-
|
|
481
|
+
yaml_skipped = scene_stats.get("yaml_skipped", 0)
|
|
482
|
+
if not (failed or yaml_skipped or skipped or timeout):
|
|
398
483
|
return
|
|
399
484
|
response["partial"] = True
|
|
400
485
|
reason_parts: list[str] = []
|
|
@@ -414,6 +499,25 @@ class SceneSearchMixin(ConfigFetchMixin):
|
|
|
414
499
|
f"their match status is unknown; this result is not "
|
|
415
500
|
f"exhaustive.{hint}"
|
|
416
501
|
)
|
|
502
|
+
if yaml_skipped:
|
|
503
|
+
# Structural, not transient, and two kinds land here: a YAML-defined
|
|
504
|
+
# scene with an ``id:`` is indistinguishable from a storage scene in
|
|
505
|
+
# the entity registry, so it survives the integration-platform
|
|
506
|
+
# filter and then 404s on the per-id endpoint; and on the
|
|
507
|
+
# registry-failed attempt-all path integration-managed scenes reach
|
|
508
|
+
# the fetch and 404 the same way. Name both so the reader knows
|
|
509
|
+
# where the definition actually lives. Mirrors the
|
|
510
|
+
# automation/script fragment in ``_apply_per_type_partial_flag``
|
|
511
|
+
# (#2292).
|
|
512
|
+
reason_parts.append(
|
|
513
|
+
f"{yaml_skipped} scene(s) not scanned (per-id config endpoint "
|
|
514
|
+
"returned 404 — these are YAML-defined scenes, or "
|
|
515
|
+
"integration-managed scenes, which that endpoint does not "
|
|
516
|
+
"expose) — their match status is unknown; this result is not "
|
|
517
|
+
"exhaustive. Their definitions live outside HA storage "
|
|
518
|
+
"(typically scenes.yaml or the owning integration); check "
|
|
519
|
+
"there if the match matters."
|
|
520
|
+
)
|
|
417
521
|
if timeout:
|
|
418
522
|
reason_parts.append(
|
|
419
523
|
f"{timeout} scene(s) not scanned (per-id fetch timed out "
|
|
@@ -446,8 +550,13 @@ class SceneSearchMixin(ConfigFetchMixin):
|
|
|
446
550
|
if scene_stats["registry_failed"]:
|
|
447
551
|
# Issue #1168 R5 blocker 11: when the registry fetch errors, the
|
|
448
552
|
# integration-platform filter is unavailable and Attempt C falls
|
|
449
|
-
# back to attempting all scenes -- surface that so
|
|
450
|
-
#
|
|
553
|
+
# back to attempting all scenes -- surface that so the elevated
|
|
554
|
+
# counts aren't mistaken for a real config outage. Since the 404
|
|
555
|
+
# taxonomy split (#2292) the bucket that swells here is
|
|
556
|
+
# ``yaml_skipped``, not ``failed``: the REST client classifies an
|
|
557
|
+
# integration-managed scene's 404 as
|
|
558
|
+
# ``SceneStorageConfigNotFoundError`` from its own registry/state
|
|
559
|
+
# lookups, which this path's failed registry walk does not affect.
|
|
451
560
|
reason_parts.append(
|
|
452
561
|
"Entity-registry fetch failed; integration-platform filter "
|
|
453
562
|
"unavailable, attempted all scenes (false-positive failures "
|