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.
Files changed (303) hide show
  1. {ha_mcp_dev-8.3.0.dev2465/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.3.0.dev2467}/PKG-INFO +1 -1
  2. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/pyproject.toml +2 -1
  3. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/client/websocket_client.py +44 -5
  4. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/auto_backup.py +65 -0
  5. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_deep.py +6 -1
  6. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_scenes.py +156 -47
  7. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_filesystem.py +90 -23
  8. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_search.py +9 -1
  9. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  10. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/LICENSE +0 -0
  11. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/MANIFEST.in +0 -0
  12. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/README.md +0 -0
  13. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/setup.cfg +0 -0
  14. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/__init__.py +0 -0
  15. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/__main__.py +0 -0
  16. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_pypi_marker +0 -0
  17. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_rich_compat.py +0 -0
  18. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/__init__.py +0 -0
  19. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/requirements.txt +0 -0
  20. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
  21. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
  22. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
  23. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
  24. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
  25. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
  26. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
  27. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
  28. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
  29. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
  30. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
  31. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
  32. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
  33. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/client.py +0 -0
  34. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
  35. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
  36. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
  37. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
  38. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
  39. {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
  40. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
  41. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
  42. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
  43. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
  44. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
  45. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
  46. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
  47. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
  48. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
  49. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
  50. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
  51. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
  52. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
  53. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
  54. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
  55. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
  56. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/server.py +0 -0
  57. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
  58. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
  59. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
  60. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
  61. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
  62. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
  63. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
  64. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
  65. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
  66. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
  67. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
  68. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
  69. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
  70. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
  71. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
  72. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
  73. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
  74. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
  75. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_vendor/websockets/version.py +0 -0
  76. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/_version.py +0 -0
  77. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/auth/__init__.py +0 -0
  78. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/auth/consent_form.py +0 -0
  79. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/auth/oidc_compat.py +0 -0
  80. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/auth/provider.py +0 -0
  81. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/backup_manager.py +0 -0
  82. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/browser_landing.py +0 -0
  83. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/client/__init__.py +0 -0
  84. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/client/rest_client.py +0 -0
  85. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/client/supervisor_client.py +0 -0
  86. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/client/websocket_listener.py +0 -0
  87. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/config.py +0 -0
  88. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  89. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  90. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  91. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  92. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  93. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  94. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/errors.py +0 -0
  95. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/hacs_auto_refresh.py +0 -0
  96. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/llm_exposure.py +0 -0
  97. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/log_filters.py +0 -0
  98. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/__init__.py +0 -0
  99. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/approval_queue.py +0 -0
  100. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/editing.py +0 -0
  101. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/evaluator.py +0 -0
  102. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/handlers.py +0 -0
  103. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/middleware.py +0 -0
  104. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/model.py +0 -0
  105. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/persistence.py +0 -0
  106. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/policy/value_sources.py +0 -0
  107. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/py.typed +0 -0
  108. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/read_only.py +0 -0
  109. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/redaction.py +0 -0
  110. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/renamed_tools.py +0 -0
  111. {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
  112. {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
  113. {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
  114. {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
  115. {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
  116. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  117. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  118. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  119. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  120. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  121. {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
  122. {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
  123. {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
  124. {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
  125. {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
  126. {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
  127. {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
  128. {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
  129. {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
  130. {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
  131. {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
  132. {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
  133. {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
  134. {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
  135. {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
  136. {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
  137. {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
  138. {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
  139. {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
  140. {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
  141. {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
  142. {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
  143. {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
  144. {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
  145. {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
  146. {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
  147. {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
  148. {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
  149. {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
  150. {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
  151. {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
  152. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/server.py +0 -0
  153. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/__init__.py +0 -0
  154. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  155. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  156. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  157. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  158. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  159. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  160. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  161. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_locale_policy.py +0 -0
  162. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  163. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  164. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_theme.py +0 -0
  165. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  166. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  167. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
  168. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  169. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  170. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
  171. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/es.json +0 -0
  172. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
  173. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/it.json +0 -0
  174. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/ko.json +0 -0
  175. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
  176. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
  177. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  178. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
  179. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/locales/tlh.json +0 -0
  180. {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
  181. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/settings.css +0 -0
  182. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/settings.html +0 -0
  183. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/settings_ui/settings.js +0 -0
  184. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/smoke_test.py +0 -0
  185. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  186. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/strict_bps.py +0 -0
  187. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/__init__.py +0 -0
  188. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/backup.py +0 -0
  189. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  190. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/best_practice_result.py +0 -0
  191. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/best_practice_variables.py +0 -0
  192. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/bulk_selector.py +0 -0
  193. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/component_api.py +0 -0
  194. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/component_config_entries.py +0 -0
  195. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/component_config_reads.py +0 -0
  196. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/component_devices.py +0 -0
  197. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/component_registries.py +0 -0
  198. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  199. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  200. {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
  201. {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
  202. {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
  203. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/config_entry_identity.py +0 -0
  204. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
  205. {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
  206. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/device_control.py +0 -0
  207. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/error_log_parsing.py +0 -0
  208. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/hacs_registration.py +0 -0
  209. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/helpers.py +0 -0
  210. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
  211. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/log_common.py +0 -0
  212. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/log_sources.py +0 -0
  213. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/log_sources_supervisor.py +0 -0
  214. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/radio/__init__.py +0 -0
  215. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/radio/base.py +0 -0
  216. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/radio/matter.py +0 -0
  217. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/radio/thread.py +0 -0
  218. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  219. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/radio/zwave.py +0 -0
  220. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/reference_validator.py +0 -0
  221. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/registry.py +0 -0
  222. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
  223. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  224. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  225. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  226. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  227. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  228. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_graph.py +0 -0
  229. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  230. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  231. {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
  232. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_addons.py +0 -0
  233. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_areas.py +0 -0
  234. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
  235. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  236. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  237. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_calendar.py +0 -0
  238. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_camera.py +0 -0
  239. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_categories.py +0 -0
  240. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_code.py +0 -0
  241. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  242. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  243. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  244. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  245. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  246. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  247. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_dev.py +0 -0
  248. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_energy.py +0 -0
  249. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_entities.py +0 -0
  250. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_groups.py +0 -0
  251. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_hacs.py +0 -0
  252. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_history.py +0 -0
  253. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_integrations.py +0 -0
  254. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_labels.py +0 -0
  255. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_logs.py +0 -0
  256. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_radio.py +0 -0
  257. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_registry.py +0 -0
  258. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_resources.py +0 -0
  259. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_security_policy.py +0 -0
  260. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_service.py +0 -0
  261. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_services.py +0 -0
  262. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_system.py +0 -0
  263. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_themes.py +0 -0
  264. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_todo.py +0 -0
  265. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_traces.py +0 -0
  266. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_updates.py +0 -0
  267. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_utility.py +0 -0
  268. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  269. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  270. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  271. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/tools_zones.py +0 -0
  272. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/util_helpers.py +0 -0
  273. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/tools/validation_middleware.py +0 -0
  274. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/transforms/__init__.py +0 -0
  275. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/transforms/categorized_search.py +0 -0
  276. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  277. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/transport_security.py +0 -0
  278. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/update_check.py +0 -0
  279. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/__init__.py +0 -0
  280. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/config_hash.py +0 -0
  281. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/config_write_lock.py +0 -0
  282. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/data_paths.py +0 -0
  283. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/domain_handlers.py +0 -0
  284. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/entity_membership.py +0 -0
  285. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  286. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  287. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/operation_manager.py +0 -0
  288. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/python_sandbox.py +0 -0
  289. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/skill_loader.py +0 -0
  290. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/utils/usage_logger.py +0 -0
  291. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/visibility/__init__.py +0 -0
  292. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/visibility/enforcement.py +0 -0
  293. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/visibility/model.py +0 -0
  294. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/visibility/persistence.py +0 -0
  295. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp/visibility/resolver.py +0 -0
  296. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  297. {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
  298. {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
  299. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  300. {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
  301. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/tests/__init__.py +0 -0
  302. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/tests/test_constants.py +0 -0
  303. {ha_mcp_dev-8.3.0.dev2465 → ha_mcp_dev-8.3.0.dev2467}/tests/test_env_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 8.3.0.dev2465
3
+ Version: 8.3.0.dev2467
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT
@@ -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.dev2465"
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
- """Cancel a pending request future if it exists."""
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
- """Cancel a stored event future."""
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 TimeoutError:
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 TimeoutError:
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 TimeoutError:
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,
@@ -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(
@@ -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 six diagnostic signals feeding the response ``partial`` /
341
+ the seven diagnostic signals feeding the response ``partial`` /
231
342
  ``partial_reason``:
232
- ``(results, failed_count, skipped_count, integration_skipped,
233
- registry_failed, timeout_count, failed_sample)``. ``failed_sample``
234
- is one representative ``summarize_fetch_error`` summary of a
235
- ``failed``-class exception (``None`` when none occurred) — see the
236
- automation/script mirror in ``_deep_search_automations`` (#1784
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 an eighth return value: the one production
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
- try:
294
- config_resp = await asyncio.wait_for(
295
- # Fetch by STORAGE key (what the endpoint indexes on),
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
- # Scene YAML/integration-managed pre-classification happens
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
- if not (failed or skipped or timeout):
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 an elevated
450
- # failed_count isn't mistaken for a real config outage.
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 "