mirror of
https://github.com/home-assistant/core.git
synced 2026-06-15 13:51:58 +02:00
Compare commits
174 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f0d5d1d526 | |||
| 2fd8222465 | |||
| c36dd62e2e | |||
| 2c3d629edb | |||
| 72d72ba428 | |||
| f9b8f05403 | |||
| 6dfe6472af | |||
| 5ac1dd8288 | |||
| 68b2a1326c | |||
| b56d261e53 | |||
| f9f37d1c2c | |||
| 0dffc84280 | |||
| 2ffcc70544 | |||
| 3c4757d944 | |||
| 1f6c45ca7a | |||
| efb08dfd78 | |||
| 65b469e185 | |||
| 00b004c329 | |||
| c51409758e | |||
| 9a3d4a0ecc | |||
| 8c94cfe124 | |||
| 8290bd2d8e | |||
| 24045c67d2 | |||
| 9f30cb3c4f | |||
| 12c3bb3482 | |||
| c164652d03 | |||
| 6c483b46fb | |||
| e86986bc83 | |||
| 0cffa7ba6b | |||
| cff58aed7d | |||
| a4e89020cf | |||
| 143b340327 | |||
| 39fe1479e2 | |||
| 07635debda | |||
| 6313450ec1 | |||
| 6e3643ebc2 | |||
| 5e2af44e05 | |||
| 6fab59c9d2 | |||
| e0041c7361 | |||
| 9e2ac3e5ca | |||
| 95f261db66 | |||
| c576d5267a | |||
| 1241a11c9d | |||
| 8f7447de58 | |||
| 1e54dba835 | |||
| 20583d6d1b | |||
| b94370ee51 | |||
| d068a2aa11 | |||
| 5d53a1f204 | |||
| 2908f37130 | |||
| a88a795ad3 | |||
| 73a36a2c47 | |||
| 054494181e | |||
| e4e8f901ab | |||
| b7a29bfa2f | |||
| 26b0079945 | |||
| 7454f40dd8 | |||
| 26b7d1e32c | |||
| f7342ea9b0 | |||
| 825d99ddaf | |||
| 401fae6bdd | |||
| 5433beeec1 | |||
| af60e248d3 | |||
| 8c452c280f | |||
| 3aec970321 | |||
| 687c91d5f4 | |||
| 377fdceb6c | |||
| 11a4533ccc | |||
| 52b2738b2a | |||
| 3fda722dbb | |||
| e01215da0e | |||
| 6c116cf3e4 | |||
| 8017e802dd | |||
| 501d956b1b | |||
| 8aca342a78 | |||
| bd68e9fbe3 | |||
| b75c839868 | |||
| 742bfb00ff | |||
| 987c19d991 | |||
| b4319c4d0c | |||
| 0fdb3ebed7 | |||
| efa3334616 | |||
| 9ec0f2fe4f | |||
| 9bc5e2b06b | |||
| 46a38cc481 | |||
| a63f2f1d20 | |||
| 744bb6a068 | |||
| d449e3e97b | |||
| 0df379704f | |||
| 4ab7ce04a8 | |||
| 210b08b637 | |||
| f0b448dc6e | |||
| b5a314bf60 | |||
| 741c342749 | |||
| f4d4df9c35 | |||
| bcbdf7b2bb | |||
| b3309ef169 | |||
| caaf5f9715 | |||
| 7ce7de3650 | |||
| 2c14c6be75 | |||
| e020f338ab | |||
| c85c2c4cd3 | |||
| c4e618e990 | |||
| 5efde60d21 | |||
| d9dc10ed81 | |||
| cb6ae03d21 | |||
| 915b78473c | |||
| 559006ba19 | |||
| bad2eed9fe | |||
| 9f1a079688 | |||
| 965a96b957 | |||
| d5791ae8b4 | |||
| 7b561934ea | |||
| cf60690fb7 | |||
| 34d175e452 | |||
| 88f1cb55d4 | |||
| 2972d9eaa5 | |||
| a9de180937 | |||
| 7a898c0eca | |||
| d3d883358c | |||
| 483f7072dd | |||
| 2db3a5024b | |||
| 0b870e104f | |||
| c5acc04860 | |||
| a1486af33a | |||
| 527c0b1fb8 | |||
| d284dff5ce | |||
| 3fbdb88b3c | |||
| 9957393f91 | |||
| 95e6c39e40 | |||
| 54b6c5c542 | |||
| 065cb7abcb | |||
| 120cc2af6a | |||
| 7dd7bae231 | |||
| f0c0e937d1 | |||
| fcf9e6be63 | |||
| 7f0d86bd4f | |||
| 96c5774bef | |||
| 6288905ca5 | |||
| 4c1dbec599 | |||
| 2a0b5ca895 | |||
| 746c8dd908 | |||
| dadfea4d62 | |||
| 53aef99921 | |||
| 88bd563a2c | |||
| c57c8fad16 | |||
| 5932a11e0c | |||
| 080492c64d | |||
| 3b8689637a | |||
| 8cd97fc60e | |||
| 7c9e5ad31b | |||
| 540e9a3d3b | |||
| 11d4b39a9e | |||
| 8379747a5b | |||
| 70a54d333c | |||
| 92b888b11d | |||
| 4b50008c73 | |||
| e4b5fcf539 | |||
| a2bdf4627f | |||
| 42c05f0998 | |||
| ee30356217 | |||
| d8860fc001 | |||
| 0bf27ad6be | |||
| 2e8e5c63e8 | |||
| 99096e4b65 | |||
| 594bcff43f | |||
| e99cb27015 | |||
| 2aa1da7216 | |||
| edbb27a912 | |||
| 4cf5509bc1 | |||
| aa1940095e | |||
| 437d33d791 | |||
| 770488f0d4 | |||
| cefbb109d2 |
+1
-1
@@ -22,4 +22,4 @@ requirements.txt linguist-generated=true
|
||||
requirements_all.txt linguist-generated=true
|
||||
requirements_test_pre_commit.txt linguist-generated=true
|
||||
script/hassfest/docker/Dockerfile linguist-generated=true
|
||||
.github/workflows/*.lock.yml linguist-generated=true
|
||||
.github/workflows/*.lock.yml linguist-generated=true merge=ours
|
||||
|
||||
@@ -14,3 +14,4 @@ updates:
|
||||
ignore:
|
||||
# Managed by gh aw compile. Version-locked to the gh-aw compiler; do not bump.
|
||||
- dependency-name: "github/gh-aw-actions/**"
|
||||
- dependency-name: "github/gh-aw-actions"
|
||||
|
||||
+235
-78
@@ -1,5 +1,5 @@
|
||||
# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"e4fcdd04986da27ef3059faa0cea3d64bb879fe12085ebfdec0041bbc31ec181","compiler_version":"v0.74.4","strict":true,"agent_id":"copilot"}
|
||||
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"d3abfe96a194bce3a523ed2093ddedd5704cdf62","version":"v0.74.4"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
|
||||
# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"e4fcdd04986da27ef3059faa0cea3d64bb879fe12085ebfdec0041bbc31ec181","body_hash":"3894ded07d5934ac5f29d160ffb1f9115cf72b6da8a7e453a4d4f69e8641a48e","compiler_version":"v0.79.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}}
|
||||
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"v0.79.6","version":"v0.79.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]}
|
||||
# ___ _ _
|
||||
# / _ \ | | (_)
|
||||
# | |_| | __ _ ___ _ __ | |_ _ ___
|
||||
@@ -14,7 +14,7 @@
|
||||
# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
|
||||
# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
|
||||
#
|
||||
# This file was automatically generated by gh-aw (v0.74.4). DO NOT EDIT.
|
||||
# This file was automatically generated by gh-aw (v0.79.6). DO NOT EDIT.
|
||||
#
|
||||
# To update this file, edit the corresponding .md file and run:
|
||||
# gh aw compile
|
||||
@@ -36,15 +36,14 @@
|
||||
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
# - github/gh-aw-actions/setup@73ed520ae4ecd087a485e1991605595978b32ac1 # v0.78.1
|
||||
# - github/gh-aw-actions/setup@v0.79.6
|
||||
#
|
||||
# Container images used:
|
||||
# - ghcr.io/github/gh-aw-firewall/agent:0.25.46
|
||||
# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46
|
||||
# - ghcr.io/github/gh-aw-firewall/squid:0.25.46
|
||||
# - ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388
|
||||
# - ghcr.io/github/github-mcp-server:v1.0.4
|
||||
# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f
|
||||
# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6
|
||||
# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4
|
||||
# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591
|
||||
# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa
|
||||
# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c
|
||||
|
||||
name: "Check requirements (AW)"
|
||||
on:
|
||||
@@ -74,9 +73,14 @@ jobs:
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
env:
|
||||
GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }}
|
||||
outputs:
|
||||
comment_id: ""
|
||||
comment_repo: ""
|
||||
daily_effective_workflow_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_exceeded == 'true' }}
|
||||
daily_effective_workflow_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_threshold || '' }}
|
||||
daily_effective_workflow_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_total_effective_tokens || '' }}
|
||||
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
|
||||
lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }}
|
||||
model: ${{ steps.generate_aw_info.outputs.model }}
|
||||
@@ -88,33 +92,35 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@73ed520ae4ecd087a485e1991605595978b32ac1 # v0.78.1
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }}
|
||||
parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }}
|
||||
safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
|
||||
env:
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Check requirements (AW)"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/check-requirements.lock.yml@${{ github.ref }}
|
||||
GH_AW_INFO_VERSION: "1.0.48"
|
||||
GH_AW_INFO_VERSION: "1.0.60"
|
||||
GH_AW_INFO_AWF_VERSION: "v0.27.2"
|
||||
GH_AW_INFO_ENGINE_ID: "copilot"
|
||||
- name: Generate agentic run info
|
||||
id: generate_aw_info
|
||||
env:
|
||||
GH_AW_INFO_ENGINE_ID: "copilot"
|
||||
GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
|
||||
GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }}
|
||||
GH_AW_INFO_VERSION: "1.0.48"
|
||||
GH_AW_INFO_AGENT_VERSION: "1.0.48"
|
||||
GH_AW_INFO_CLI_VERSION: "v0.74.4"
|
||||
GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
|
||||
GH_AW_INFO_VERSION: "1.0.60"
|
||||
GH_AW_INFO_AGENT_VERSION: "1.0.60"
|
||||
GH_AW_INFO_CLI_VERSION: "v0.79.6"
|
||||
GH_AW_INFO_WORKFLOW_NAME: "Check requirements (AW)"
|
||||
GH_AW_INFO_EXPERIMENTAL: "false"
|
||||
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
|
||||
GH_AW_INFO_STAGED: "false"
|
||||
GH_AW_INFO_ALLOWED_DOMAINS: '["python"]'
|
||||
GH_AW_INFO_FIREWALL_ENABLED: "true"
|
||||
GH_AW_INFO_AWF_VERSION: "v0.25.46"
|
||||
GH_AW_INFO_AWF_VERSION: "v0.27.2"
|
||||
GH_AW_INFO_AWMG_VERSION: ""
|
||||
GH_AW_INFO_FIREWALL_TYPE: "squid"
|
||||
GH_AW_COMPILED_STRICT: "true"
|
||||
@@ -125,6 +131,24 @@ jobs:
|
||||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||||
const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
|
||||
await main(core, context);
|
||||
- name: Check daily workflow token guardrail
|
||||
id: daily-effective-workflow-guardrail
|
||||
if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_WORKFLOW_NAME: "Check requirements (AW)"
|
||||
GH_AW_WORKFLOW_ID: "check-requirements"
|
||||
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }}
|
||||
GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }}
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
|
||||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||||
const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs');
|
||||
await main();
|
||||
- name: Validate COPILOT_GITHUB_TOKEN secret
|
||||
id: validate-secret
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
|
||||
@@ -137,6 +161,7 @@ jobs:
|
||||
sparse-checkout: |
|
||||
.github
|
||||
.agents
|
||||
.antigravity
|
||||
.claude
|
||||
.codex
|
||||
.crush
|
||||
@@ -147,8 +172,8 @@ jobs:
|
||||
fetch-depth: 1
|
||||
- name: Save agent config folders for base branch restoration
|
||||
env:
|
||||
GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi"
|
||||
GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
|
||||
GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi"
|
||||
GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
|
||||
# poutine:ignore untrusted_checkout_exec
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
|
||||
- name: Check workflow lock file
|
||||
@@ -166,7 +191,7 @@ jobs:
|
||||
- name: Check compile-agentic version
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_COMPILED_VERSION: "v0.74.4"
|
||||
GH_AW_COMPILED_VERSION: "v0.79.6"
|
||||
with:
|
||||
script: |
|
||||
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
|
||||
@@ -189,20 +214,20 @@ jobs:
|
||||
run: |
|
||||
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
|
||||
{
|
||||
cat << 'GH_AW_PROMPT_2fc32253e89940f3_EOF'
|
||||
cat << 'GH_AW_PROMPT_781cf5b2f30d6d93_EOF'
|
||||
<system>
|
||||
GH_AW_PROMPT_2fc32253e89940f3_EOF
|
||||
GH_AW_PROMPT_781cf5b2f30d6d93_EOF
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
|
||||
cat << 'GH_AW_PROMPT_2fc32253e89940f3_EOF'
|
||||
cat << 'GH_AW_PROMPT_781cf5b2f30d6d93_EOF'
|
||||
<safe-output-tools>
|
||||
Tools: add_comment, missing_tool, missing_data, noop
|
||||
</safe-output-tools>
|
||||
GH_AW_PROMPT_2fc32253e89940f3_EOF
|
||||
GH_AW_PROMPT_781cf5b2f30d6d93_EOF
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
|
||||
cat << 'GH_AW_PROMPT_2fc32253e89940f3_EOF'
|
||||
cat << 'GH_AW_PROMPT_781cf5b2f30d6d93_EOF'
|
||||
<github-context>
|
||||
The following GitHub context information is available for this workflow:
|
||||
{{#if github.actor}}
|
||||
@@ -231,12 +256,12 @@ jobs:
|
||||
{{/if}}
|
||||
</github-context>
|
||||
|
||||
GH_AW_PROMPT_2fc32253e89940f3_EOF
|
||||
GH_AW_PROMPT_781cf5b2f30d6d93_EOF
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
|
||||
cat << 'GH_AW_PROMPT_2fc32253e89940f3_EOF'
|
||||
cat << 'GH_AW_PROMPT_781cf5b2f30d6d93_EOF'
|
||||
</system>
|
||||
{{#runtime-import .github/workflows/check-requirements.md}}
|
||||
GH_AW_PROMPT_2fc32253e89940f3_EOF
|
||||
GH_AW_PROMPT_781cf5b2f30d6d93_EOF
|
||||
} > "$GH_AW_PROMPT"
|
||||
- name: Interpolate variables and render templates
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
@@ -304,12 +329,15 @@ jobs:
|
||||
include-hidden-files: true
|
||||
path: |
|
||||
/tmp/gh-aw/aw_info.json
|
||||
/tmp/gh-aw/model_multipliers.json
|
||||
/tmp/gh-aw/models.json
|
||||
/tmp/gh-aw/aw-prompts/prompt.txt
|
||||
/tmp/gh-aw/aw-prompts/prompt-template.txt
|
||||
/tmp/gh-aw/aw-prompts/prompt-import-tree.json
|
||||
/tmp/gh-aw/github_rate_limits.jsonl
|
||||
/tmp/gh-aw/base
|
||||
/tmp/gh-aw/.github/agents
|
||||
/tmp/gh-aw/.github/skills
|
||||
if-no-files-found: ignore
|
||||
retention-days: 1
|
||||
|
||||
@@ -317,6 +345,7 @@ jobs:
|
||||
needs:
|
||||
- activation
|
||||
- extract_pr_number
|
||||
if: needs.activation.outputs.daily_effective_workflow_exceeded != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -324,6 +353,7 @@ jobs:
|
||||
pull-requests: read
|
||||
concurrency:
|
||||
group: "gh-aw-copilot-${{ github.workflow }}"
|
||||
queue: max
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
GH_AW_ASSETS_ALLOWED_EXTS: ""
|
||||
@@ -332,24 +362,27 @@ jobs:
|
||||
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
|
||||
GH_AW_WORKFLOW_ID_SANITIZED: checkrequirements
|
||||
outputs:
|
||||
agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }}
|
||||
agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }}
|
||||
ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }}
|
||||
aic: ${{ steps.parse-mcp-gateway.outputs.aic }}
|
||||
ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }}
|
||||
checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
|
||||
effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }}
|
||||
effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }}
|
||||
has_patch: ${{ steps.collect_output.outputs.has_patch }}
|
||||
inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }}
|
||||
mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }}
|
||||
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
|
||||
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
|
||||
model: ${{ needs.activation.outputs.model }}
|
||||
model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }}
|
||||
model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
|
||||
output: ${{ steps.collect_output.outputs.output }}
|
||||
output_types: ${{ steps.collect_output.outputs.output_types }}
|
||||
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
|
||||
setup-span-id: ${{ steps.setup.outputs.span-id }}
|
||||
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
|
||||
unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@73ed520ae4ecd087a485e1991605595978b32ac1 # v0.78.1
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -358,7 +391,8 @@ jobs:
|
||||
env:
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Check requirements (AW)"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/check-requirements.lock.yml@${{ github.ref }}
|
||||
GH_AW_INFO_VERSION: "1.0.48"
|
||||
GH_AW_INFO_VERSION: "1.0.60"
|
||||
GH_AW_INFO_AWF_VERSION: "v0.27.2"
|
||||
GH_AW_INFO_ENGINE_ID: "copilot"
|
||||
- name: Set runtime paths
|
||||
id: set-runtime-paths
|
||||
@@ -403,7 +437,7 @@ jobs:
|
||||
- name: Checkout PR branch
|
||||
id: checkout-pr
|
||||
if: |
|
||||
github.event.pull_request || github.event.issue.pull_request
|
||||
github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request'
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
@@ -415,11 +449,11 @@ jobs:
|
||||
const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
|
||||
await main();
|
||||
- name: Install GitHub Copilot CLI
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.48
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60
|
||||
env:
|
||||
GH_HOST: github.com
|
||||
- name: Install AWF binary
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.46
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2
|
||||
- name: Parse integrity filter lists
|
||||
id: parse-guard-vars
|
||||
env:
|
||||
@@ -435,24 +469,28 @@ jobs:
|
||||
- name: Restore agent config folders from base branch
|
||||
if: steps.checkout-pr.outcome == 'success'
|
||||
env:
|
||||
GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi"
|
||||
GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
|
||||
GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi"
|
||||
GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
|
||||
- name: Restore inline sub-agents from activation artifact
|
||||
env:
|
||||
GH_AW_SUB_AGENT_DIR: ".github/agents"
|
||||
GH_AW_SUB_AGENT_EXT: ".agent.md"
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh"
|
||||
- name: Restore inline skills from activation artifact
|
||||
env:
|
||||
GH_AW_SKILL_DIR: ".github/skills"
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
|
||||
- name: Download container images
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.46 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46 ghcr.io/github/gh-aw-firewall/squid:0.25.46 ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388 ghcr.io/github/github-mcp-server:v1.0.4 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c
|
||||
- name: Generate Safe Outputs Config
|
||||
run: |
|
||||
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
|
||||
mkdir -p /tmp/gh-aw/safeoutputs
|
||||
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
|
||||
cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_eaae5443153d0b45_EOF'
|
||||
cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f496a449c5dccca1_EOF'
|
||||
{"add_comment":{"max":1,"target":"${{ needs.extract_pr_number.outputs.pr_number }}"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
|
||||
GH_AW_SAFE_OUTPUTS_CONFIG_eaae5443153d0b45_EOF
|
||||
GH_AW_SAFE_OUTPUTS_CONFIG_f496a449c5dccca1_EOF
|
||||
- name: Generate Safe Outputs Tools
|
||||
env:
|
||||
GH_AW_TOOLS_META_JSON: |
|
||||
@@ -640,16 +678,16 @@ jobs:
|
||||
* ) DOCKER_SOCK_PATH=/var/run/docker.sock ;;
|
||||
esac
|
||||
DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0')
|
||||
export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.9'
|
||||
export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25'
|
||||
|
||||
mkdir -p /home/runner/.copilot
|
||||
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
|
||||
cat << GH_AW_MCP_CONFIG_d99df59573a98681_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
|
||||
cat << GH_AW_MCP_CONFIG_f09adf73c5e58a42_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"type": "stdio",
|
||||
"container": "ghcr.io/github/github-mcp-server:v1.0.4",
|
||||
"container": "ghcr.io/github/github-mcp-server:v1.1.2",
|
||||
"env": {
|
||||
"GITHUB_HOST": "\${GITHUB_SERVER_URL}",
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
|
||||
@@ -688,7 +726,7 @@ jobs:
|
||||
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
|
||||
}
|
||||
}
|
||||
GH_AW_MCP_CONFIG_d99df59573a98681_EOF
|
||||
GH_AW_MCP_CONFIG_f09adf73c5e58a42_EOF
|
||||
- name: Mount MCP servers as CLIs
|
||||
id: mount-mcp-clis
|
||||
continue-on-error: true
|
||||
@@ -717,29 +755,48 @@ jobs:
|
||||
run: |
|
||||
set -o pipefail
|
||||
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
|
||||
trap 'rm -f /home/runner/.copilot/settings.json' EXIT
|
||||
mkdir -p /home/runner/.copilot
|
||||
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > /home/runner/.copilot/settings.json
|
||||
touch /tmp/gh-aw/agent-step-summary.md
|
||||
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
|
||||
export GH_AW_NODE_BIN
|
||||
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
|
||||
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
|
||||
printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.46/awf-config.schema.json","network":{"allowDomains":["*.pythonhosted.org","anaconda.org","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","binstar.org","bootstrap.pypa.io","conda.anaconda.org","conda.binstar.org","files.pythonhosted.org","github.com","host.docker.internal","pip.pypa.io","pypi.org","pypi.python.org","raw.githubusercontent.com","registry.npmjs.org","repo.anaconda.com","repo.continuum.io","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"auto":["large"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.46"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
|
||||
GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}"
|
||||
printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.pythonhosted.org\",\"anaconda.org\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"binstar.org\",\"bootstrap.pypa.io\",\"conda.anaconda.org\",\"conda.binstar.org\",\"files.pythonhosted.org\",\"github.com\",\"host.docker.internal\",\"pip.pypa.io\",\"pypi.org\",\"pypi.python.org\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"repo.anaconda.com\",\"repo.continuum.io\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
|
||||
GH_AW_MODEL_MULTIPLIERS_PATH="/tmp/gh-aw/model_multipliers.json" node "${RUNNER_TEMP}/gh-aw/actions/merge_awf_model_multipliers.cjs"
|
||||
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
|
||||
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
|
||||
GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS=""
|
||||
if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
|
||||
GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw"
|
||||
fi
|
||||
GH_AW_TOOL_CACHE_MOUNT=""
|
||||
GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"
|
||||
if [ -d "$GH_AW_TOOL_CACHE" ]; then
|
||||
if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
|
||||
GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
|
||||
fi
|
||||
elif [ -d "/home/runner/work/_tool" ]; then
|
||||
GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro"
|
||||
fi
|
||||
# shellcheck disable=SC1003
|
||||
sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \
|
||||
-- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
|
||||
sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \
|
||||
-- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
|
||||
env:
|
||||
AWF_REFLECT_ENABLED: 1
|
||||
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
|
||||
COPILOT_API_KEY: dummy-byok-key-for-offline-mode
|
||||
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
|
||||
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
||||
COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }}
|
||||
COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
|
||||
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
|
||||
GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
|
||||
GH_AW_PHASE: agent
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
|
||||
GH_AW_VERSION: v0.74.4
|
||||
GH_AW_TIMEOUT_MINUTES: 20
|
||||
GH_AW_VERSION: v0.79.6
|
||||
GITHUB_API_URL: ${{ github.api_url }}
|
||||
GITHUB_AW: true
|
||||
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
|
||||
@@ -753,12 +810,13 @@ jobs:
|
||||
GIT_AUTHOR_NAME: github-actions[bot]
|
||||
GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
|
||||
GIT_COMMITTER_NAME: github-actions[bot]
|
||||
RUNNER_TEMP: ${{ runner.temp }}
|
||||
XDG_CONFIG_HOME: /home/runner
|
||||
- name: Detect Copilot errors
|
||||
id: detect-copilot-errors
|
||||
- name: Detect agent errors
|
||||
if: always()
|
||||
id: detect-agent-errors
|
||||
continue-on-error: true
|
||||
run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs"
|
||||
run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs"
|
||||
- name: Configure Git credentials
|
||||
env:
|
||||
REPO_NAME: ${{ github.repository }}
|
||||
@@ -939,7 +997,7 @@ jobs:
|
||||
- safe_outputs
|
||||
if: >
|
||||
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
|
||||
needs.activation.outputs.stale_lock_file_failed == 'true')
|
||||
needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_effective_workflow_exceeded == 'true')
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -958,7 +1016,7 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@73ed520ae4ecd087a485e1991605595978b32ac1 # v0.78.1
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -967,7 +1025,8 @@ jobs:
|
||||
env:
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Check requirements (AW)"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/check-requirements.lock.yml@${{ github.ref }}
|
||||
GH_AW_INFO_VERSION: "1.0.48"
|
||||
GH_AW_INFO_VERSION: "1.0.60"
|
||||
GH_AW_INFO_AWF_VERSION: "v0.27.2"
|
||||
GH_AW_INFO_ENGINE_ID: "copilot"
|
||||
- name: Download agent output artifact
|
||||
id: download-agent-output
|
||||
@@ -983,6 +1042,40 @@ jobs:
|
||||
mkdir -p /tmp/gh-aw/
|
||||
find "/tmp/gh-aw/" -type f -print
|
||||
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
|
||||
- name: Collect usage artifact files
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
run: |
|
||||
mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection
|
||||
echo "Usage artifact source file status:"
|
||||
for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do
|
||||
[ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file"
|
||||
done
|
||||
[ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true
|
||||
[ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true
|
||||
[ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true
|
||||
[ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
|
||||
[ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
|
||||
[ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
|
||||
[ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
|
||||
[ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
|
||||
[ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
|
||||
[ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl
|
||||
[ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl
|
||||
find /tmp/gh-aw/usage -type f -print | sort
|
||||
- name: Upload usage artifact
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: usage
|
||||
path: |
|
||||
/tmp/gh-aw/usage/aw-info.jsonl
|
||||
/tmp/gh-aw/usage/agent_usage.jsonl
|
||||
/tmp/gh-aw/usage/detection_usage.jsonl
|
||||
/tmp/gh-aw/usage/agent/token_usage.jsonl
|
||||
/tmp/gh-aw/usage/detection/token_usage.jsonl
|
||||
if-no-files-found: ignore
|
||||
- name: Process no-op messages
|
||||
id: noop
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
@@ -990,9 +1083,14 @@ jobs:
|
||||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_NOOP_MAX: "1"
|
||||
GH_AW_WORKFLOW_NAME: "Check requirements (AW)"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/check-requirements.md"
|
||||
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
|
||||
GH_AW_NOOP_REPORT_AS_ISSUE: "true"
|
||||
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
|
||||
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
|
||||
GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
|
||||
GH_AW_WORKFLOW_ID: "check-requirements"
|
||||
with:
|
||||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
@@ -1006,6 +1104,7 @@ jobs:
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_WORKFLOW_NAME: "Check requirements (AW)"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/check-requirements.md"
|
||||
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }}
|
||||
GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }}
|
||||
@@ -1023,6 +1122,7 @@ jobs:
|
||||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_MISSING_TOOL_CREATE_ISSUE: "true"
|
||||
GH_AW_WORKFLOW_NAME: "Check requirements (AW)"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/check-requirements.md"
|
||||
with:
|
||||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
@@ -1037,6 +1137,7 @@ jobs:
|
||||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true"
|
||||
GH_AW_WORKFLOW_NAME: "Check requirements (AW)"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/check-requirements.md"
|
||||
with:
|
||||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
@@ -1051,6 +1152,7 @@ jobs:
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_WORKFLOW_NAME: "Check requirements (AW)"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/check-requirements.md"
|
||||
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
|
||||
GH_AW_WORKFLOW_ID: "check-requirements"
|
||||
@@ -1059,7 +1161,11 @@ jobs:
|
||||
GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }}
|
||||
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
|
||||
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
|
||||
GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.effective_tokens_rate_limit_error || 'false' }}
|
||||
GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }}
|
||||
GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }}
|
||||
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
|
||||
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
|
||||
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
|
||||
GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }}
|
||||
GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }}
|
||||
GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
|
||||
@@ -1067,12 +1173,14 @@ jobs:
|
||||
GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com"
|
||||
GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
|
||||
GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }}
|
||||
GH_AW_DAILY_EFFECTIVE_WORKFLOW_EXCEEDED: ${{ needs.activation.outputs.daily_effective_workflow_exceeded }}
|
||||
GH_AW_DAILY_EFFECTIVE_WORKFLOW_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_effective_workflow_total_effective_tokens }}
|
||||
GH_AW_DAILY_EFFECTIVE_WORKFLOW_THRESHOLD: ${{ needs.activation.outputs.daily_effective_workflow_threshold }}
|
||||
GH_AW_GROUP_REPORTS: "false"
|
||||
GH_AW_FAILURE_REPORT_AS_ISSUE: "true"
|
||||
GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true"
|
||||
GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true"
|
||||
GH_AW_TIMEOUT_MINUTES: "20"
|
||||
GH_AW_MAX_EFFECTIVE_TOKENS: "25000000"
|
||||
with:
|
||||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
@@ -1091,13 +1199,14 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
aic: ${{ steps.parse_detection_token_usage.outputs.aic }}
|
||||
detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }}
|
||||
detection_reason: ${{ steps.detection_conclusion.outputs.reason }}
|
||||
detection_success: ${{ steps.detection_conclusion.outputs.success }}
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@73ed520ae4ecd087a485e1991605595978b32ac1 # v0.78.1
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -1106,7 +1215,8 @@ jobs:
|
||||
env:
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Check requirements (AW)"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/check-requirements.lock.yml@${{ github.ref }}
|
||||
GH_AW_INFO_VERSION: "1.0.48"
|
||||
GH_AW_INFO_VERSION: "1.0.60"
|
||||
GH_AW_INFO_AWF_VERSION: "v0.27.2"
|
||||
GH_AW_INFO_ENGINE_ID: "copilot"
|
||||
- name: Download agent output artifact
|
||||
id: download-agent-output
|
||||
@@ -1133,7 +1243,7 @@ jobs:
|
||||
rm -rf /tmp/gh-aw/sandbox/firewall/logs
|
||||
rm -rf /tmp/gh-aw/sandbox/firewall/audit
|
||||
- name: Download container images
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.46 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46 ghcr.io/github/gh-aw-firewall/squid:0.25.46
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591
|
||||
- name: Check if detection needed
|
||||
id: detection_guard
|
||||
if: always()
|
||||
@@ -1158,7 +1268,11 @@ jobs:
|
||||
if: always() && steps.detection_guard.outputs.run_detection == 'true'
|
||||
run: |
|
||||
mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
|
||||
rm -f /tmp/gh-aw/agent_usage.json
|
||||
cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
|
||||
if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then
|
||||
echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context."
|
||||
fi
|
||||
cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
|
||||
for f in /tmp/gh-aw/aw-*.patch; do
|
||||
[ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
|
||||
@@ -1192,11 +1306,11 @@ jobs:
|
||||
node-version: '24'
|
||||
package-manager-cache: false
|
||||
- name: Install GitHub Copilot CLI
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.48
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60
|
||||
env:
|
||||
GH_HOST: github.com
|
||||
- name: Install AWF binary
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.46
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2
|
||||
- name: Execute GitHub Copilot CLI
|
||||
if: always() && steps.detection_guard.outputs.run_detection == 'true'
|
||||
continue-on-error: true
|
||||
@@ -1206,27 +1320,46 @@ jobs:
|
||||
run: |
|
||||
set -o pipefail
|
||||
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
|
||||
trap 'rm -f /home/runner/.copilot/settings.json' EXIT
|
||||
mkdir -p /home/runner/.copilot
|
||||
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > /home/runner/.copilot/settings.json
|
||||
touch /tmp/gh-aw/agent-step-summary.md
|
||||
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
|
||||
export GH_AW_NODE_BIN
|
||||
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
|
||||
(umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
|
||||
printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.46/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.46"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
|
||||
GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}"
|
||||
printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
|
||||
GH_AW_MODEL_MULTIPLIERS_PATH="/tmp/gh-aw/model_multipliers.json" node "${RUNNER_TEMP}/gh-aw/actions/merge_awf_model_multipliers.cjs"
|
||||
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
|
||||
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
|
||||
GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS=""
|
||||
if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
|
||||
GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw"
|
||||
fi
|
||||
GH_AW_TOOL_CACHE_MOUNT=""
|
||||
GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"
|
||||
if [ -d "$GH_AW_TOOL_CACHE" ]; then
|
||||
if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
|
||||
GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
|
||||
fi
|
||||
elif [ -d "/home/runner/work/_tool" ]; then
|
||||
GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro"
|
||||
fi
|
||||
# shellcheck disable=SC1003
|
||||
sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \
|
||||
-- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
|
||||
sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \
|
||||
-- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
|
||||
env:
|
||||
AWF_REFLECT_ENABLED: 1
|
||||
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
|
||||
COPILOT_API_KEY: dummy-byok-key-for-offline-mode
|
||||
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
|
||||
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
||||
COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }}
|
||||
COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
|
||||
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
|
||||
GH_AW_PHASE: detection
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
GH_AW_VERSION: v0.74.4
|
||||
GH_AW_TIMEOUT_MINUTES: 20
|
||||
GH_AW_VERSION: v0.79.6
|
||||
GITHUB_API_URL: ${{ github.api_url }}
|
||||
GITHUB_AW: true
|
||||
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
|
||||
@@ -1239,7 +1372,21 @@ jobs:
|
||||
GIT_AUTHOR_NAME: github-actions[bot]
|
||||
GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
|
||||
GIT_COMMITTER_NAME: github-actions[bot]
|
||||
RUNNER_TEMP: ${{ runner.temp }}
|
||||
XDG_CONFIG_HOME: /home/runner
|
||||
- name: Parse threat detection token usage for step summary
|
||||
id: parse_detection_token_usage
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
|
||||
with:
|
||||
script: |
|
||||
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
|
||||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||||
const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
|
||||
await main();
|
||||
- name: Upload threat detection log
|
||||
if: always() && steps.detection_guard.outputs.run_detection == 'true'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
@@ -1293,6 +1440,7 @@ jobs:
|
||||
- name: Configure GH_HOST for enterprise compatibility
|
||||
id: ghes-host-config
|
||||
shell: bash
|
||||
# zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input.
|
||||
run: |
|
||||
# Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct
|
||||
# GitHub instance (GHES/GHEC). On github.com this is a harmless no-op.
|
||||
@@ -1323,14 +1471,15 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@73ed520ae4ecd087a485e1991605595978b32ac1 # v0.78.1
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
env:
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Check requirements (AW)"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/check-requirements.lock.yml@${{ github.ref }}
|
||||
GH_AW_INFO_VERSION: "1.0.48"
|
||||
GH_AW_INFO_VERSION: "1.0.60"
|
||||
GH_AW_INFO_AWF_VERSION: "v0.27.2"
|
||||
GH_AW_INFO_ENGINE_ID: "copilot"
|
||||
- name: Check team membership for workflow
|
||||
id: check_membership
|
||||
@@ -1358,17 +1507,22 @@ jobs:
|
||||
discussions: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
timeout-minutes: 15
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }}
|
||||
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
|
||||
GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
|
||||
GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/check-requirements"
|
||||
GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }}
|
||||
GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }}
|
||||
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
|
||||
GH_AW_ENGINE_ID: "copilot"
|
||||
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
|
||||
GH_AW_ENGINE_VERSION: "1.0.48"
|
||||
GH_AW_ENGINE_VERSION: "1.0.60"
|
||||
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
|
||||
GH_AW_WORKFLOW_ID: "check-requirements"
|
||||
GH_AW_WORKFLOW_NAME: "Check requirements (AW)"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/check-requirements.md"
|
||||
outputs:
|
||||
code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }}
|
||||
code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}
|
||||
@@ -1381,7 +1535,7 @@ jobs:
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@73ed520ae4ecd087a485e1991605595978b32ac1 # v0.78.1
|
||||
uses: github/gh-aw-actions/setup@v0.79.6
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
@@ -1390,7 +1544,8 @@ jobs:
|
||||
env:
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Check requirements (AW)"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/check-requirements.lock.yml@${{ github.ref }}
|
||||
GH_AW_INFO_VERSION: "1.0.48"
|
||||
GH_AW_INFO_VERSION: "1.0.60"
|
||||
GH_AW_INFO_AWF_VERSION: "v0.27.2"
|
||||
GH_AW_INFO_ENGINE_ID: "copilot"
|
||||
- name: Download agent output artifact
|
||||
id: download-agent-output
|
||||
@@ -1409,6 +1564,7 @@ jobs:
|
||||
- name: Configure GH_HOST for enterprise compatibility
|
||||
id: ghes-host-config
|
||||
shell: bash
|
||||
# zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input.
|
||||
run: |
|
||||
# Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct
|
||||
# GitHub instance (GHES/GHEC). On github.com this is a harmless no-op.
|
||||
@@ -1420,6 +1576,7 @@ jobs:
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
|
||||
GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,files.pythonhosted.org,github.com,host.docker.internal,pip.pypa.io,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,telemetry.enterprise.githubcopilot.com"
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_API_URL: ${{ github.api_url }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.15.15
|
||||
rev: v0.15.16
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args:
|
||||
|
||||
@@ -26,5 +26,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aioacaia"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aioacaia==0.1.17"]
|
||||
"requirements": ["aioacaia==0.1.18"]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/acer_projector",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["serialx==1.8.0"]
|
||||
"requirements": ["serialx==1.8.2"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/acmeda",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiopulse"],
|
||||
"requirements": ["aiopulse==0.4.6"]
|
||||
"requirements": ["aiopulse==0.4.7"]
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioamazondevices"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aioamazondevices==14.0.3"]
|
||||
"requirements": ["aioamazondevices==14.0.4"]
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["anova_wifi"],
|
||||
"requirements": ["anova-wifi==0.17.0"]
|
||||
"requirements": ["anova-wifi==0.17.1"]
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["anthemav"],
|
||||
"requirements": ["anthemav==1.4.1"]
|
||||
"requirements": ["anthemav==1.4.2"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/assist_satellite",
|
||||
"integration_type": "entity",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["hassil==3.7.0"]
|
||||
"requirements": ["hassil==3.8.0"]
|
||||
}
|
||||
|
||||
@@ -30,5 +30,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pubnub", "yalexs"],
|
||||
"requirements": ["yalexs==9.2.7", "yalexs-ble==3.3.0"]
|
||||
"requirements": ["yalexs==9.2.7", "yalexs-ble==3.3.1"]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
"""The BleBox devices integration."""
|
||||
|
||||
import logging
|
||||
|
||||
from blebox_uniapi.box import Box
|
||||
from blebox_uniapi.error import Error
|
||||
from blebox_uniapi.error import ConnectionError, Error, HttpError, UnauthorizedRequest
|
||||
from blebox_uniapi.session import ApiHost
|
||||
|
||||
from homeassistant.const import (
|
||||
@@ -14,14 +12,16 @@ from homeassistant.const import (
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.exceptions import (
|
||||
ConfigEntryAuthFailed,
|
||||
ConfigEntryError,
|
||||
ConfigEntryNotReady,
|
||||
)
|
||||
|
||||
from .const import DEFAULT_SETUP_TIMEOUT
|
||||
from .coordinator import BleBoxConfigEntry, BleBoxCoordinator
|
||||
from .helpers import get_maybe_authenticated_session
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
@@ -50,9 +50,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: BleBoxConfigEntry) -> bo
|
||||
|
||||
try:
|
||||
product = await Box.async_from_host(api_host)
|
||||
except Error as ex:
|
||||
_LOGGER.error("Identify failed at %s:%d (%s)", api_host.host, api_host.port, ex)
|
||||
except UnauthorizedRequest as ex:
|
||||
raise ConfigEntryAuthFailed from ex
|
||||
except (
|
||||
ConnectionError,
|
||||
HttpError,
|
||||
) as ex:
|
||||
raise ConfigEntryNotReady from ex
|
||||
except Error as ex:
|
||||
raise ConfigEntryError from ex
|
||||
|
||||
coordinator = BleBoxCoordinator(hass, entry, product)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
@@ -59,6 +59,8 @@ class BleBoxBinarySensorEntity(BleBoxEntity[BinarySensorFeature], BinarySensorEn
|
||||
"""Initialize a BleBox binary sensor feature."""
|
||||
super().__init__(coordinator, feature)
|
||||
self.entity_description = description
|
||||
if feature.name:
|
||||
self._attr_name = feature.name
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
|
||||
@@ -41,6 +41,8 @@ async def async_setup_entry(
|
||||
class BleBoxButtonEntity(BleBoxEntity[blebox_uniapi.button.Button], ButtonEntity):
|
||||
"""Representation of BleBox buttons."""
|
||||
|
||||
_attr_name = None
|
||||
|
||||
def __init__(
|
||||
self, coordinator: BleBoxCoordinator, feature: blebox_uniapi.button.Button
|
||||
) -> None:
|
||||
|
||||
@@ -51,6 +51,7 @@ async def async_setup_entry(
|
||||
class BleBoxClimateEntity(BleBoxEntity[blebox_uniapi.climate.Climate], ClimateEntity):
|
||||
"""Representation of a BleBox climate feature (saunaBox)."""
|
||||
|
||||
_attr_name = None
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Config flow for BleBox devices integration."""
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@@ -26,6 +27,7 @@ from .const import (
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_SETUP_TIMEOUT,
|
||||
DOMAIN,
|
||||
INVALID_AUTH,
|
||||
UNKNOWN,
|
||||
UNSUPPORTED_VERSION,
|
||||
)
|
||||
@@ -46,6 +48,7 @@ STEP_SCHEMA = vol.Schema(
|
||||
LOG_MSG = {
|
||||
UNSUPPORTED_VERSION: "Outdated firmware",
|
||||
CANNOT_CONNECT: "Failed to identify device",
|
||||
INVALID_AUTH: "Authentication failed",
|
||||
UNKNOWN: "Unknown error while identifying device",
|
||||
}
|
||||
|
||||
@@ -87,7 +90,7 @@ class BleBoxConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
except UnauthorizedRequest as ex:
|
||||
return None, self.handle_step_exception(
|
||||
ex, schema, host, port, CANNOT_CONNECT, _LOGGER.error, step_id
|
||||
ex, schema, host, port, INVALID_AUTH, _LOGGER.error, step_id
|
||||
)
|
||||
except Error as ex:
|
||||
return None, self.handle_step_exception(
|
||||
@@ -115,6 +118,8 @@ class BleBoxConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
try:
|
||||
product = await Box.async_from_host(api_host)
|
||||
except UnauthorizedRequest:
|
||||
return self.async_abort(reason="authorization_required")
|
||||
except UnsupportedBoxVersion:
|
||||
return self.async_abort(reason="unsupported_device_version")
|
||||
except UnsupportedBoxResponse:
|
||||
@@ -246,3 +251,58 @@ class BleBoxConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
reconfigure_entry,
|
||||
data_updates=data_updates,
|
||||
)
|
||||
|
||||
async def async_step_reauth(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reauthentication upon an API authentication error."""
|
||||
self.context["title_placeholders"] = {
|
||||
"name": self._get_reauth_entry().title,
|
||||
"host": entry_data[CONF_HOST],
|
||||
}
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reauthentication confirmation."""
|
||||
errors: dict[str, str] = {}
|
||||
reauth_entry = self._get_reauth_entry()
|
||||
host = reauth_entry.data[CONF_HOST]
|
||||
port = reauth_entry.data[CONF_PORT]
|
||||
|
||||
if user_input is not None:
|
||||
username = user_input.get(CONF_USERNAME)
|
||||
password = user_input.get(CONF_PASSWORD)
|
||||
websession = get_maybe_authenticated_session(self.hass, password, username)
|
||||
api_host = ApiHost(
|
||||
host, port, DEFAULT_SETUP_TIMEOUT, websession, self.hass.loop, _LOGGER
|
||||
)
|
||||
try:
|
||||
await Box.async_from_host(api_host)
|
||||
except UnauthorizedRequest:
|
||||
errors["base"] = INVALID_AUTH
|
||||
except Error:
|
||||
errors["base"] = CANNOT_CONNECT
|
||||
except RuntimeError:
|
||||
errors["base"] = UNKNOWN
|
||||
else:
|
||||
return self.async_update_reload_and_abort(
|
||||
reauth_entry,
|
||||
data_updates={
|
||||
CONF_USERNAME: username,
|
||||
CONF_PASSWORD: password,
|
||||
},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reauth_confirm",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Inclusive(CONF_USERNAME, "auth"): str,
|
||||
vol.Inclusive(CONF_PASSWORD, "auth"): str,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
description_placeholders={"address": f"{host}:{port}"},
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ DEFAULT_SETUP_TIMEOUT = 10
|
||||
# translation strings
|
||||
ADDRESS_ALREADY_CONFIGURED = "address_already_configured"
|
||||
CANNOT_CONNECT = "cannot_connect"
|
||||
INVALID_AUTH = "invalid_auth"
|
||||
UNSUPPORTED_VERSION = "unsupported_version"
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@ from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from blebox_uniapi.box import Box
|
||||
from blebox_uniapi.error import Error
|
||||
from blebox_uniapi.error import Error, UnauthorizedRequest
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN
|
||||
@@ -40,6 +41,8 @@ class BleBoxCoordinator(DataUpdateCoordinator[None]):
|
||||
"""Fetch data from the BleBox device."""
|
||||
try:
|
||||
await self.box.async_update_data()
|
||||
except UnauthorizedRequest as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
except Error as err:
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
|
||||
@@ -74,6 +74,8 @@ async def async_setup_entry(
|
||||
class BleBoxCoverEntity(BleBoxEntity[blebox_uniapi.cover.Cover], CoverEntity):
|
||||
"""Representation of a BleBox cover feature."""
|
||||
|
||||
_attr_name = None
|
||||
|
||||
def __init__(
|
||||
self, coordinator: BleBoxCoordinator, feature: blebox_uniapi.cover.Cover
|
||||
) -> None:
|
||||
|
||||
@@ -12,11 +12,12 @@ from .coordinator import BleBoxCoordinator
|
||||
class BleBoxEntity[_FeatureT: Feature](CoordinatorEntity[BleBoxCoordinator]):
|
||||
"""Implements a common class for entities representing a BleBox feature."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, coordinator: BleBoxCoordinator, feature: _FeatureT) -> None:
|
||||
"""Initialize a BleBox entity."""
|
||||
super().__init__(coordinator)
|
||||
self._feature = feature
|
||||
self._attr_name = feature.full_name
|
||||
self._attr_unique_id = feature.unique_id
|
||||
product = feature.product
|
||||
self._attr_device_info = DeviceInfo(
|
||||
|
||||
@@ -71,6 +71,11 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
|
||||
super().__init__(coordinator, feature)
|
||||
if feature.effect_list:
|
||||
self._attr_supported_features = LightEntityFeature.EFFECT
|
||||
if feature.index is not None:
|
||||
self._attr_translation_key = "channel"
|
||||
self._attr_translation_placeholders = {"index": str(feature.index + 1)}
|
||||
else:
|
||||
self._attr_name = None
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""BleBox sensor entities."""
|
||||
|
||||
from collections import Counter
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
@@ -23,6 +24,7 @@ from homeassistant.const import (
|
||||
UnitOfEnergy,
|
||||
UnitOfFrequency,
|
||||
UnitOfPower,
|
||||
UnitOfReactiveEnergy,
|
||||
UnitOfReactivePower,
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
@@ -67,6 +69,7 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = (
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="temperature",
|
||||
translation_key="temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -95,50 +98,72 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = (
|
||||
native_unit_of_measurement=LIGHT_LUX,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="forwardReactiveEnergy",
|
||||
translation_key="forward_reactive_energy",
|
||||
device_class=SensorDeviceClass.REACTIVE_ENERGY,
|
||||
native_unit_of_measurement=UnitOfReactiveEnergy.KILO_VOLT_AMPERE_REACTIVE_HOUR,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="reverseReactiveEnergy",
|
||||
translation_key="reverse_reactive_energy",
|
||||
device_class=SensorDeviceClass.REACTIVE_ENERGY,
|
||||
native_unit_of_measurement=UnitOfReactiveEnergy.KILO_VOLT_AMPERE_REACTIVE_HOUR,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="forwardActiveEnergy",
|
||||
translation_key="forward_active_energy",
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="reverseActiveEnergy",
|
||||
translation_key="reverse_active_energy",
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="reactivePower",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
translation_key="reactive_power",
|
||||
device_class=SensorDeviceClass.REACTIVE_POWER,
|
||||
native_unit_of_measurement=UnitOfReactivePower.VOLT_AMPERE_REACTIVE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="activePower",
|
||||
translation_key="active_power",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="apparentPower",
|
||||
translation_key="apparent_power",
|
||||
device_class=SensorDeviceClass.APPARENT_POWER,
|
||||
native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="voltage",
|
||||
translation_key="voltage",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="current",
|
||||
translation_key="current",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.MILLIAMPERE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="frequency",
|
||||
translation_key="frequency",
|
||||
device_class=SensorDeviceClass.FREQUENCY,
|
||||
native_unit_of_measurement=UnitOfFrequency.HERTZ,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -172,10 +197,20 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up a BleBox entry."""
|
||||
|
||||
coordinator = config_entry.runtime_data
|
||||
features = coordinator.box.features.get("sensors", [])
|
||||
counts = Counter(f.device_class for f in features)
|
||||
entities = [
|
||||
BleBoxSensorEntity(coordinator, feature, description)
|
||||
for feature in coordinator.box.features.get("sensors", [])
|
||||
BleBoxSensorEntity(
|
||||
coordinator,
|
||||
feature,
|
||||
description,
|
||||
feature.index
|
||||
if counts[feature.device_class] > 1 and feature.index
|
||||
else None,
|
||||
)
|
||||
for feature in features
|
||||
for description in SENSOR_TYPES
|
||||
if description.key == feature.device_class
|
||||
]
|
||||
@@ -192,10 +227,16 @@ class BleBoxSensorEntity(BleBoxEntity[blebox_uniapi.sensor.BaseSensor], SensorEn
|
||||
coordinator: BleBoxCoordinator,
|
||||
feature: blebox_uniapi.sensor.BaseSensor,
|
||||
description: BleBoxSensorEntityDescription,
|
||||
index: int | None = None,
|
||||
) -> None:
|
||||
"""Initialize a BleBox sensor feature."""
|
||||
super().__init__(coordinator, feature)
|
||||
self.entity_description = description
|
||||
if feature.name:
|
||||
self._attr_name = feature.name
|
||||
elif index is not None and description.translation_key:
|
||||
self._attr_translation_key = f"{description.translation_key}_n"
|
||||
self._attr_translation_placeholders = {"index": str(index)}
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
|
||||
@@ -3,16 +3,33 @@
|
||||
"abort": {
|
||||
"address_already_configured": "A BleBox device is already configured at {address}.",
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"authorization_required": "The BleBox device requires authentication.",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||
"unique_id_mismatch": "The device identifier does not match the previously configured device."
|
||||
"unique_id_mismatch": "The device identifier does not match the previously configured device.",
|
||||
"unsupported_device_response": "The BleBox device returned an unrecognized response.",
|
||||
"unsupported_device_version": "[%key:component::blebox::config::error::unsupported_version%]"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||
"unsupported_version": "BleBox device has outdated firmware. Please upgrade it first."
|
||||
},
|
||||
"flow_title": "{name} ({host})",
|
||||
"step": {
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
},
|
||||
"data_description": {
|
||||
"password": "The password for your BleBox device.",
|
||||
"username": "The username for your BleBox device."
|
||||
},
|
||||
"description": "Enter credentials for the BleBox device at {address}.",
|
||||
"title": "Reauthenticate your BleBox device"
|
||||
},
|
||||
"reconfigure": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::ip%]",
|
||||
@@ -30,13 +47,24 @@
|
||||
"port": "[%key:common::config_flow::data::port%]",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "The IP address of your BleBox device.",
|
||||
"password": "The password for your BleBox device.",
|
||||
"port": "The port of your BleBox device.",
|
||||
"username": "The username for your BleBox device."
|
||||
},
|
||||
"description": "Set up your BleBox to integrate with Home Assistant.",
|
||||
"title": "Set up your BleBox device"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"light": { "channel": { "name": "Channel {index}" } },
|
||||
"sensor": {
|
||||
"active_power": { "name": "Active power" },
|
||||
"active_power_n": { "name": "Active power {index}" },
|
||||
"apparent_power": { "name": "Apparent power" },
|
||||
"apparent_power_n": { "name": "Apparent power {index}" },
|
||||
"co2_level": {
|
||||
"name": "Carbon dioxide level",
|
||||
"state": {
|
||||
@@ -49,7 +77,18 @@
|
||||
"unhealthy": "Unhealthy"
|
||||
}
|
||||
},
|
||||
"current": { "name": "Current" },
|
||||
"current_n": { "name": "Current {index}" },
|
||||
"forward_active_energy": { "name": "Forward active energy" },
|
||||
"forward_active_energy_n": { "name": "Forward active energy {index}" },
|
||||
"forward_reactive_energy": { "name": "Forward reactive energy" },
|
||||
"forward_reactive_energy_n": {
|
||||
"name": "Forward reactive energy {index}"
|
||||
},
|
||||
"frequency": { "name": "Frequency" },
|
||||
"frequency_n": { "name": "Frequency {index}" },
|
||||
"open_status": {
|
||||
"name": "Open status",
|
||||
"state": {
|
||||
"ajar": "Ajar",
|
||||
"closed": "[%key:common::state::closed%]",
|
||||
@@ -57,7 +96,20 @@
|
||||
"open": "[%key:common::state::open%]",
|
||||
"unclosed_or_unlocked": "Unclosed or unlocked"
|
||||
}
|
||||
}
|
||||
},
|
||||
"power_consumption": { "name": "Energy last hour" },
|
||||
"reactive_power": { "name": "Reactive power" },
|
||||
"reactive_power_n": { "name": "Reactive power {index}" },
|
||||
"reverse_active_energy": { "name": "Reverse active energy" },
|
||||
"reverse_active_energy_n": { "name": "Reverse active energy {index}" },
|
||||
"reverse_reactive_energy": { "name": "Reverse reactive energy" },
|
||||
"reverse_reactive_energy_n": {
|
||||
"name": "Reverse reactive energy {index}"
|
||||
},
|
||||
"temperature": { "name": "Temperature" },
|
||||
"temperature_n": { "name": "Temperature {index}" },
|
||||
"voltage": { "name": "Voltage" },
|
||||
"voltage_n": { "name": "Voltage {index}" }
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
|
||||
@@ -9,6 +9,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import BleBoxConfigEntry
|
||||
from .coordinator import BleBoxCoordinator
|
||||
from .entity import BleBoxEntity
|
||||
from .util import blebox_command
|
||||
|
||||
@@ -34,6 +35,16 @@ class BleBoxSwitchEntity(BleBoxEntity[blebox_uniapi.switch.Switch], SwitchEntity
|
||||
|
||||
_attr_device_class = SwitchDeviceClass.SWITCH
|
||||
|
||||
_attr_name = None
|
||||
|
||||
def __init__(
|
||||
self, coordinator: BleBoxCoordinator, feature: blebox_uniapi.switch.Switch
|
||||
) -> None:
|
||||
"""Initialize a BleBox switch feature."""
|
||||
super().__init__(coordinator, feature)
|
||||
if feature.name:
|
||||
self._attr_name = feature.name
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return whether switch is on."""
|
||||
|
||||
@@ -21,5 +21,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["blinkpy"],
|
||||
"requirements": ["blinkpy==0.25.2"]
|
||||
"requirements": ["blinkpy==0.25.6"]
|
||||
}
|
||||
|
||||
@@ -26,6 +26,12 @@
|
||||
"description": "The credentials for {username} need to be updated",
|
||||
"title": "Re-authenticate Blink"
|
||||
},
|
||||
"reconfigure": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["bluecurrent_api"],
|
||||
"requirements": ["bluecurrent-api==1.3.2"]
|
||||
"requirements": ["bluecurrent-api==1.3.3"]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/bluesound",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["pyblu==2.0.6"],
|
||||
"requirements": ["pyblu==2.0.8"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_musc._tcp.local."
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["boschshcpy"],
|
||||
"requirements": ["boschshcpy==0.2.107"],
|
||||
"requirements": ["boschshcpy==0.2.111"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"name": "bosch shc*",
|
||||
|
||||
@@ -125,7 +125,7 @@ class BringTodoListEntity(BringBaseEntity, TodoListEntity):
|
||||
await self.coordinator.async_refresh()
|
||||
|
||||
async def async_update_todo_item(self, item: TodoItem) -> None:
|
||||
"""Update an item to the To-do list.
|
||||
"""Update an item in the To-do list.
|
||||
|
||||
Bring has an internal 'recent' list which we want to use instead of a todo list
|
||||
status, therefore completed todo list items are matched to the recent list and
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["buienradar", "vincenty"],
|
||||
"requirements": ["buienradar==1.0.6"]
|
||||
"requirements": ["buienradar==1.0.9"]
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ from homeassistant.components.websocket_api import (
|
||||
ActiveConnection,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.const import CONF_EVENT, STATE_OFF, STATE_ON
|
||||
from homeassistant.core import (
|
||||
CALLBACK_TYPE,
|
||||
HomeAssistant,
|
||||
@@ -45,7 +45,6 @@ from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.json import JsonValueType
|
||||
|
||||
from .const import (
|
||||
CONF_EVENT,
|
||||
DATA_COMPONENT,
|
||||
DOMAIN,
|
||||
EVENT_DESCRIPTION,
|
||||
|
||||
@@ -13,9 +13,6 @@ if TYPE_CHECKING:
|
||||
DOMAIN = "calendar"
|
||||
DATA_COMPONENT: HassKey[EntityComponent[CalendarEntity]] = HassKey(DOMAIN)
|
||||
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
CONF_EVENT = "event"
|
||||
|
||||
|
||||
class CalendarEntityFeature(IntFlag):
|
||||
"""Supported features of the calendar entity."""
|
||||
|
||||
@@ -31,12 +31,24 @@ async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
|
||||
data["relayer_region"] = client.relayer_region
|
||||
data["remote_enabled"] = client.prefs.remote_enabled
|
||||
data["remote_connected"] = cloud.remote.is_connected
|
||||
data["remote_server"] = cloud.remote.snitun_server
|
||||
data["alexa_enabled"] = client.prefs.alexa_enabled
|
||||
data["google_enabled"] = client.prefs.google_enabled
|
||||
data["cloud_ice_servers_enabled"] = client.prefs.cloud_ice_servers_enabled
|
||||
data["remote_server"] = cloud.remote.snitun_server
|
||||
data["certificate_status"] = cloud.remote.certificate_status
|
||||
data["instance_id"] = client.prefs.instance_id
|
||||
data["iot_state"] = cloud.iot.state
|
||||
data["iot_tries"] = cloud.iot.tries
|
||||
|
||||
if (cert := cloud.remote.certificate) is not None:
|
||||
data["certificate_expire_date"] = cert.expire_date
|
||||
data["certificate_fingerprint"] = cert.fingerprint
|
||||
if cert.alternative_names:
|
||||
data["certificate_alternative_names"] = cert.alternative_names
|
||||
|
||||
if (disconnect := cloud.iot.last_disconnect_reason) is not None:
|
||||
data["iot_last_disconnect_clean"] = disconnect.clean
|
||||
data["iot_last_disconnect_reason"] = disconnect.reason
|
||||
|
||||
data["can_reach_cert_server"] = system_health.async_check_can_reach_url(
|
||||
hass, f"https://{cloud.acme_server}/directory"
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aiocomelit"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aiocomelit==2.0.3"]
|
||||
"requirements": ["aiocomelit==2.0.5"]
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ from hassil.recognize import RecognizeResult
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import MATCH_ALL
|
||||
from homeassistant.const import MATCH_ALL, SERVICE_RELOAD
|
||||
from homeassistant.core import (
|
||||
HomeAssistant,
|
||||
ServiceCall,
|
||||
@@ -53,7 +53,6 @@ from .const import (
|
||||
METADATA_CUSTOM_FILE,
|
||||
METADATA_CUSTOM_SENTENCE,
|
||||
SERVICE_PROCESS,
|
||||
SERVICE_RELOAD,
|
||||
ConversationEntityFeature,
|
||||
)
|
||||
from .default_agent import async_setup_default_agent
|
||||
|
||||
@@ -19,8 +19,6 @@ ATTR_AGENT_ID = "agent_id"
|
||||
ATTR_CONVERSATION_ID = "conversation_id"
|
||||
|
||||
SERVICE_PROCESS = "process"
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
SERVICE_RELOAD = "reload"
|
||||
|
||||
DATA_COMPONENT: HassKey[EntityComponent[ConversationEntity]] = HassKey(DOMAIN)
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/conversation",
|
||||
"integration_type": "entity",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["hassil==3.7.0", "home-assistant-intents==2026.6.1"]
|
||||
"requirements": ["hassil==3.8.0", "home-assistant-intents==2026.6.1"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Diagnostics support for Daikin."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD, CONF_UUID
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import KEY_MAC
|
||||
from .coordinator import DaikinConfigEntry
|
||||
|
||||
TO_REDACT_ENTRY = {CONF_API_KEY, CONF_HOST, CONF_PASSWORD, CONF_UUID, KEY_MAC}
|
||||
TO_REDACT_DEVICE = {"mac"}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: DaikinConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
device = entry.runtime_data.device
|
||||
return {
|
||||
"entry_data": async_redact_data(dict(entry.data), TO_REDACT_ENTRY),
|
||||
"device": {
|
||||
"values": async_redact_data(dict(device.values), TO_REDACT_DEVICE),
|
||||
"support_away_mode": device.support_away_mode,
|
||||
"support_advanced_modes": device.support_advanced_modes,
|
||||
"support_fan_rate": device.support_fan_rate,
|
||||
"support_swing_mode": device.support_swing_mode,
|
||||
"support_outside_temperature": device.support_outside_temperature,
|
||||
"support_humidity": device.support_humidity,
|
||||
"support_energy_consumption": device.support_energy_consumption,
|
||||
"support_compressor_frequency": device.support_compressor_frequency,
|
||||
},
|
||||
}
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["datadog"],
|
||||
"requirements": ["datadog==0.52.0"]
|
||||
"requirements": ["datadog==0.52.1"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["debugpy==1.8.17"]
|
||||
"requirements": ["debugpy==1.8.21"]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["denonavr"],
|
||||
"requirements": ["denonavr==1.3.2"],
|
||||
"requirements": ["denonavr==1.3.3"],
|
||||
"ssdp": [
|
||||
{
|
||||
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["HomeControl", "Mydevolo", "MprmRest", "MprmWebsocket", "Mprm"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["devolo-home-control-api==0.19.0"],
|
||||
"requirements": ["devolo-home-control-api==0.19.1"],
|
||||
"zeroconf": ["_dvl-deviceapi._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"quality_scale": "internal",
|
||||
"requirements": [
|
||||
"aiodhcpwatcher==1.2.7",
|
||||
"aiodiscover==3.3.1",
|
||||
"aiodiscover==3.3.2",
|
||||
"cached-ipaddress==1.1.2"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pydroplet==2.3.4"],
|
||||
"requirements": ["pydroplet==2.4.0"],
|
||||
"zeroconf": ["_droplet._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -19,11 +19,19 @@ from .coordinator import DucoConfigEntry
|
||||
TO_REDACT = {
|
||||
CONF_HOST,
|
||||
"mac",
|
||||
"Mac",
|
||||
"host_name",
|
||||
"HostName",
|
||||
"serial_board_box",
|
||||
"SerialBoardBox",
|
||||
"serial_board_comm",
|
||||
"SerialBoardComm",
|
||||
"serial_duco_box",
|
||||
"SerialDucoBox",
|
||||
"serial_duco_comm",
|
||||
"SerialDucoComm",
|
||||
"WifiApKey",
|
||||
"WifiApSsid",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -95,7 +95,12 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
value_fn=lambda node: node.sensor.co2 if node.sensor else None,
|
||||
node_types=(NodeType.UCCO2, NodeType.VLVCO2, NodeType.VLVCO2RH),
|
||||
node_types=(
|
||||
NodeType.BSCO2,
|
||||
NodeType.UCCO2,
|
||||
NodeType.VLVCO2,
|
||||
NodeType.VLVCO2RH,
|
||||
),
|
||||
),
|
||||
DucoSensorEntityDescription(
|
||||
key="iaq_co2",
|
||||
@@ -104,7 +109,12 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda node: node.sensor.iaq_co2 if node.sensor else None,
|
||||
node_types=(NodeType.UCCO2, NodeType.VLVCO2, NodeType.VLVCO2RH),
|
||||
node_types=(
|
||||
NodeType.BSCO2,
|
||||
NodeType.UCCO2,
|
||||
NodeType.VLVCO2,
|
||||
NodeType.VLVCO2RH,
|
||||
),
|
||||
),
|
||||
DucoSensorEntityDescription(
|
||||
key="humidity",
|
||||
|
||||
@@ -31,6 +31,29 @@ async def _async_get_write_requests_remaining(
|
||||
return {"type": "failed", "error": "unreachable"}
|
||||
|
||||
|
||||
def _entry_write_requests_remaining_key(config_entry: DucoConfigEntry) -> str:
|
||||
"""Return the identifying label for a config entry quota."""
|
||||
identifier = config_entry.unique_id or config_entry.entry_id
|
||||
return f"{config_entry.title or config_entry.entry_id} ({identifier})"
|
||||
|
||||
|
||||
async def _async_get_write_requests_remaining_summary(
|
||||
config_entries: list[DucoConfigEntry],
|
||||
) -> str:
|
||||
"""Get a per-entry write-request summary for system health."""
|
||||
# Keep one translated system health label; multiple Duco boxes are
|
||||
# summarized in the value to avoid ambiguous per-entry labels.
|
||||
summaries: list[str] = []
|
||||
for config_entry in config_entries:
|
||||
result = await _async_get_write_requests_remaining(config_entry)
|
||||
summaries.append(
|
||||
f"{_entry_write_requests_remaining_key(config_entry)}: "
|
||||
f"{result if not isinstance(result, dict) else f'Failed: {result["error"]}'}"
|
||||
)
|
||||
|
||||
return "; ".join(summaries)
|
||||
|
||||
|
||||
async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
|
||||
"""Get info for the info page."""
|
||||
config_entries: list[DucoConfigEntry] = hass.config_entries.async_loaded_entries(
|
||||
@@ -40,8 +63,15 @@ async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
|
||||
if not config_entries:
|
||||
return {}
|
||||
|
||||
if len(config_entries) == 1:
|
||||
return {
|
||||
"write_requests_remaining": _async_get_write_requests_remaining(
|
||||
config_entries[0]
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
"write_requests_remaining": _async_get_write_requests_remaining(
|
||||
config_entries[0]
|
||||
"write_requests_remaining": _async_get_write_requests_remaining_summary(
|
||||
config_entries
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["eheimdigital"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["eheimdigital==1.6.0"],
|
||||
"requirements": ["eheimdigital==1.7.0"],
|
||||
"zeroconf": [
|
||||
{ "name": "eheimdigital._http._tcp.local.", "type": "_http._tcp.local." }
|
||||
]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyenphase"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pyenphase==2.4.8"],
|
||||
"requirements": ["pyenphase==2.4.9"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_enphase-envoy._tcp.local."
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"requirements": [
|
||||
"aioesphomeapi==45.3.1",
|
||||
"esphome-dashboard-api==1.3.0",
|
||||
"bleak-esphome==3.9.1"
|
||||
"bleak-esphome==3.9.4"
|
||||
],
|
||||
"zeroconf": ["_esphomelib._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: CometBlueConfigEntry) ->
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, address)},
|
||||
connections={(dr.CONNECTION_BLUETOOTH, address)},
|
||||
name=f"{ble_device_info['model']} {cometblue_device.device.address}",
|
||||
manufacturer=ble_device_info["manufacturer"],
|
||||
model=ble_device_info["model"],
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["bleak", "fjaraskupan"],
|
||||
"requirements": ["fjaraskupan==2.3.3"]
|
||||
"requirements": ["fjaraskupan==2.3.4"]
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
"discovery_confirm": {
|
||||
"description": "Do you want to set up {model} {id} ({ipaddr})?"
|
||||
},
|
||||
"pick_device": {
|
||||
"data": {
|
||||
"device": "[%key:common::config_flow::data::device%]"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"data_description_password": "Password for the FRITZ!Box.",
|
||||
"data_description_port": "Leave empty to use the default port.",
|
||||
"data_description_ssl": "Use SSL to connect to the FRITZ!Box.",
|
||||
"data_description_username": "Username for the FRITZ!Box.",
|
||||
"data_description_username": "Username for the FRITZ!Box. FRITZ!Powerline devices ignore this information and accept any value.",
|
||||
"data_feature_device_tracking": "Enable network device tracking"
|
||||
},
|
||||
"config": {
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["fyta_cli"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["fyta_cli==0.7.2"]
|
||||
"requirements": ["fyta_cli==0.7.3"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/geniushub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["geniushubclient"],
|
||||
"requirements": ["geniushub-client==0.7.1"]
|
||||
"requirements": ["geniushub-client==0.7.4"]
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"device": "[%key:common::config_flow::data::device%]",
|
||||
"ip_address": "[%key:common::config_flow::data::ip%]",
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["gtts"],
|
||||
"requirements": ["gTTS==2.5.3"]
|
||||
"requirements": ["gTTS==2.5.4"]
|
||||
}
|
||||
|
||||
@@ -151,6 +151,13 @@ class HolidayCalendarEntity(CalendarEntity):
|
||||
"""Set up first update."""
|
||||
self._update_state_and_setup_listener()
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Cancel listener when removing."""
|
||||
await super().async_will_remove_from_hass()
|
||||
if self.unsub:
|
||||
self.unsub()
|
||||
self.unsub = None
|
||||
|
||||
def update_event(self, now: datetime) -> CalendarEvent | None:
|
||||
"""Return the next upcoming event."""
|
||||
next_holiday = None
|
||||
|
||||
@@ -27,6 +27,7 @@ from homematicip.device import (
|
||||
PassageDetector,
|
||||
PresenceDetectorIndoor,
|
||||
RoomControlDeviceAnalog,
|
||||
RotaryHandleSensor,
|
||||
SmokeDetector,
|
||||
SoilMoistureSensorInterface,
|
||||
SwitchMeasuring,
|
||||
@@ -166,6 +167,7 @@ ILLUMINATION_DEVICE_ATTRIBUTES = {
|
||||
}
|
||||
|
||||
TILT_STATE_VALUES = ["neutral", "tilted", "non_neutral"]
|
||||
WINDOW_STATE_VALUES = ["open", "closed", "tilted"]
|
||||
|
||||
|
||||
def get_device_handlers(hap: HomematicipHAP) -> dict[type, Callable]:
|
||||
@@ -204,6 +206,9 @@ def get_device_handlers(hap: HomematicipHAP) -> dict[type, Callable]:
|
||||
RoomControlDeviceAnalog: lambda device: [
|
||||
HomematicipTemperatureSensor(hap, device),
|
||||
],
|
||||
RotaryHandleSensor: lambda device: [
|
||||
HomematicipWindowStateSensor(hap, device),
|
||||
],
|
||||
LightSensor: lambda device: [
|
||||
HomematicipIlluminanceSensor(hap, device),
|
||||
],
|
||||
@@ -498,6 +503,24 @@ class HomematicipTiltStateSensor(HomematicipGenericEntity, SensorEntity):
|
||||
return state_attr
|
||||
|
||||
|
||||
class HomematicipWindowStateSensor(HomematicipGenericEntity, SensorEntity):
|
||||
"""Representation of the HomematicIP rotary handle window state sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.ENUM
|
||||
_attr_options = WINDOW_STATE_VALUES
|
||||
_attr_translation_key = "window_state"
|
||||
|
||||
def __init__(self, hap: HomematicipHAP, device: RotaryHandleSensor) -> None:
|
||||
"""Initialize the window state sensor."""
|
||||
super().__init__(hap, device, feature_id="window_state")
|
||||
|
||||
@property
|
||||
def native_value(self) -> str | None:
|
||||
"""Return the state."""
|
||||
window_state = getattr(self._device, "windowState", None)
|
||||
return window_state.lower() if window_state is not None else None
|
||||
|
||||
|
||||
class HomematicipFloorTerminalBlockMechanicChannelValve(
|
||||
HomematicipGenericEntity, SensorEntity
|
||||
):
|
||||
|
||||
@@ -98,6 +98,14 @@
|
||||
"non_neutral": "Non-neutral",
|
||||
"tilted": "Tilted"
|
||||
}
|
||||
},
|
||||
"window_state": {
|
||||
"name": "Window state",
|
||||
"state": {
|
||||
"closed": "[%key:common::state::closed%]",
|
||||
"open": "[%key:common::state::open%]",
|
||||
"tilted": "Tilted"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -50,14 +50,12 @@ def homevolt_exception_handler[_HomevoltEntityT: HomevoltEntity, **_P](
|
||||
translation_key="auth_failed",
|
||||
) from error
|
||||
except HomevoltConnectionError as error:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="communication_error",
|
||||
translation_placeholders={"error": str(error)},
|
||||
) from error
|
||||
except HomevoltError as error:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="unknown_error",
|
||||
|
||||
@@ -172,10 +172,10 @@
|
||||
"message": "[%key:common::config_flow::error::invalid_auth%]"
|
||||
},
|
||||
"communication_error": {
|
||||
"message": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
"message": "Error communicating with the Homevolt battery: {error}"
|
||||
},
|
||||
"unknown_error": {
|
||||
"message": "[%key:common::config_flow::error::unknown%]"
|
||||
"message": "Unknown error from the Homevolt battery: {error}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ import time
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
from urllib.parse import urlparse
|
||||
import uuid
|
||||
import warnings
|
||||
|
||||
from aiohttp import ClientError, ClientResponse, ClientSession, web
|
||||
from aiohttp.hdrs import AUTHORIZATION
|
||||
import jwt
|
||||
from jwt.warnings import InsecureKeyLengthWarning
|
||||
from py_vapid import Vapid
|
||||
from pywebpush import WebPusher, WebPushException, webpush_async
|
||||
import voluptuous as vol
|
||||
@@ -325,7 +327,8 @@ class HTML5PushCallbackView(HomeAssistantView):
|
||||
if target_check.get(ATTR_TARGET) in self.registrations:
|
||||
possible_target = self.registrations[target_check[ATTR_TARGET]]
|
||||
key = possible_target["subscription"]["keys"]["auth"]
|
||||
with suppress(jwt.exceptions.DecodeError):
|
||||
with suppress(jwt.exceptions.DecodeError), warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", InsecureKeyLengthWarning)
|
||||
return jwt.decode(token, key, algorithms=["ES256", "HS256"])
|
||||
|
||||
return self.json_message(
|
||||
@@ -585,7 +588,9 @@ def add_jwt(timestamp: int, target: str, tag: str, jwt_secret: str) -> str:
|
||||
ATTR_TARGET: target,
|
||||
ATTR_TAG: tag,
|
||||
}
|
||||
return jwt.encode(jwt_claims, jwt_secret)
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", InsecureKeyLengthWarning)
|
||||
return jwt.encode(jwt_claims, jwt_secret)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]"
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"id": "Hue bridge"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "The hostname or IP address of your Hue bridge."
|
||||
|
||||
@@ -85,6 +85,7 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
|
||||
|
||||
entity_description = LightEntityDescription(
|
||||
key="hue_grouped_light",
|
||||
translation_key="hue_grouped_light",
|
||||
has_entity_name=True,
|
||||
name=None,
|
||||
)
|
||||
|
||||
@@ -166,8 +166,10 @@ class HueLightLevelSensor(HueSensorBase):
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> int:
|
||||
def native_value(self) -> int | None:
|
||||
"""Return the value reported by the sensor."""
|
||||
if self.resource.light.value is None:
|
||||
return None
|
||||
# Light level in 10000 log10 (lux) +1 measured by sensor. Logarithm
|
||||
# scale used because the human eye adjusts to light levels and small
|
||||
# changes at low lux levels are more noticeable than at high lux
|
||||
|
||||
@@ -536,6 +536,11 @@ class PowerViewShadeTiltOnly(PowerViewShadeWithTiltBase):
|
||||
"""Return if the cover is closed."""
|
||||
return self.positions.tilt <= CLOSED_POSITION
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if shade position data is available."""
|
||||
return super().available and self.positions.tilt is not None
|
||||
|
||||
|
||||
class PowerViewShadeTopDown(PowerViewShadeBase):
|
||||
"""Representation of a shade that lowers from the roof to the floor.
|
||||
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
|
||||
from huum.const import SaunaStatus
|
||||
|
||||
from homeassistant.components.number import NumberEntity
|
||||
from homeassistant.components.number import NumberDeviceClass, NumberEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -34,7 +34,9 @@ class HuumSteamer(HuumBaseEntity, NumberEntity):
|
||||
"""Representation of a steamer."""
|
||||
|
||||
_attr_translation_key = "humidity"
|
||||
_attr_native_max_value = 10
|
||||
_attr_device_class = NumberDeviceClass.HUMIDITY
|
||||
_attr_native_unit_of_measurement = "%"
|
||||
_attr_native_max_value = 40
|
||||
_attr_native_min_value = 0
|
||||
_attr_native_step = 1
|
||||
|
||||
@@ -47,7 +49,7 @@ class HuumSteamer(HuumBaseEntity, NumberEntity):
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
"""Return the current value."""
|
||||
return self.coordinator.data.target_humidity
|
||||
return self.coordinator.data.humidity
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Update the current value."""
|
||||
|
||||
@@ -7,6 +7,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import CONF_OEM, DEFAULT_OEM
|
||||
from .coordinator import HypontechConfigEntry, HypontechDataCoordinator
|
||||
|
||||
_PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||
@@ -19,6 +20,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: HypontechConfigEntry) ->
|
||||
entry.data[CONF_USERNAME],
|
||||
entry.data[CONF_PASSWORD],
|
||||
session,
|
||||
oem=int(entry.data.get(CONF_OEM, DEFAULT_OEM)),
|
||||
)
|
||||
try:
|
||||
await hypontech_cloud.connect()
|
||||
|
||||
@@ -4,18 +4,29 @@ from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from hyponcloud import AuthenticationError, HyponCloud
|
||||
from hyponcloud import KNOWN_OEMS, AdminInfo, AuthenticationError, HyponCloud
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_USER, ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.selector import (
|
||||
SelectOptionDict,
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
SelectSelectorMode,
|
||||
)
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import CONF_OEM, DEFAULT_OEM, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
OEM_OPTIONS = [
|
||||
SelectOptionDict(value=str(oem.id), label=f"{oem.name} ({oem.monitoring_url})")
|
||||
for oem in KNOWN_OEMS
|
||||
]
|
||||
|
||||
STEP_REAUTH_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
@@ -23,52 +34,128 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
)
|
||||
|
||||
|
||||
def _data_schema(default_oem: int = DEFAULT_OEM) -> vol.Schema:
|
||||
"""Return the config flow data schema."""
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
vol.Required(CONF_OEM, default=str(default_oem)): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=OEM_OPTIONS,
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _entry_data(user_input: Mapping[str, Any]) -> dict[str, Any]:
|
||||
"""Normalize config entry data from user input."""
|
||||
return {**user_input, CONF_OEM: int(user_input[CONF_OEM])}
|
||||
|
||||
|
||||
def _unique_id(account_id: str, oem: int) -> str:
|
||||
"""Return a backwards-compatible unique id for the account and OEM."""
|
||||
if oem == DEFAULT_OEM:
|
||||
return account_id
|
||||
return f"{oem}:{account_id}"
|
||||
|
||||
|
||||
class HypontechConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Hypontech Cloud."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the config flow."""
|
||||
self._default_oem = DEFAULT_OEM
|
||||
|
||||
async def _async_validate_input(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> tuple[AdminInfo | None, dict[str, str]]:
|
||||
"""Validate user input."""
|
||||
errors: dict[str, str] = {}
|
||||
session = async_get_clientsession(self.hass)
|
||||
hypon = HyponCloud(
|
||||
entry_data[CONF_USERNAME],
|
||||
entry_data[CONF_PASSWORD],
|
||||
session,
|
||||
oem=entry_data[CONF_OEM],
|
||||
)
|
||||
try:
|
||||
await hypon.connect()
|
||||
return await hypon.get_admin_info(), errors
|
||||
except AuthenticationError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except TimeoutError, ConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
return None, errors
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] = {}
|
||||
default_oem = self._default_oem
|
||||
if user_input is not None:
|
||||
session = async_get_clientsession(self.hass)
|
||||
hypon = HyponCloud(
|
||||
user_input[CONF_USERNAME], user_input[CONF_PASSWORD], session
|
||||
)
|
||||
try:
|
||||
await hypon.connect()
|
||||
admin_info = await hypon.get_admin_info()
|
||||
except AuthenticationError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except TimeoutError, ConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
await self.async_set_unique_id(admin_info.id)
|
||||
entry_data = _entry_data(user_input)
|
||||
default_oem = entry_data[CONF_OEM]
|
||||
admin_info, errors = await self._async_validate_input(entry_data)
|
||||
if admin_info is not None:
|
||||
await self.async_set_unique_id(
|
||||
_unique_id(admin_info.id, entry_data[CONF_OEM])
|
||||
)
|
||||
if self.source == SOURCE_USER:
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_USERNAME],
|
||||
data=user_input,
|
||||
title=entry_data[CONF_USERNAME],
|
||||
data=entry_data,
|
||||
)
|
||||
self._abort_if_unique_id_mismatch(reason="wrong_account")
|
||||
return self.async_update_reload_and_abort(
|
||||
self._get_reauth_entry(),
|
||||
data_updates={
|
||||
CONF_USERNAME: user_input[CONF_USERNAME],
|
||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||
CONF_USERNAME: entry_data[CONF_USERNAME],
|
||||
CONF_PASSWORD: entry_data[CONF_PASSWORD],
|
||||
},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||
step_id="user", data_schema=_data_schema(default_oem), errors=errors
|
||||
)
|
||||
|
||||
async def async_step_reauth(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reauthentication."""
|
||||
return await self.async_step_user()
|
||||
self._default_oem = int(entry_data.get(CONF_OEM, DEFAULT_OEM))
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reauthentication confirmation."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
entry_data = {**user_input, CONF_OEM: self._default_oem}
|
||||
admin_info, errors = await self._async_validate_input(entry_data)
|
||||
if admin_info is not None:
|
||||
await self.async_set_unique_id(
|
||||
_unique_id(admin_info.id, entry_data[CONF_OEM])
|
||||
)
|
||||
self._abort_if_unique_id_mismatch(reason="wrong_account")
|
||||
return self.async_update_reload_and_abort(
|
||||
self._get_reauth_entry(),
|
||||
data_updates={
|
||||
CONF_USERNAME: entry_data[CONF_USERNAME],
|
||||
CONF_PASSWORD: entry_data[CONF_PASSWORD],
|
||||
},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reauth_confirm",
|
||||
data_schema=STEP_REAUTH_DATA_SCHEMA,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
@@ -4,4 +4,7 @@ from logging import Logger, getLogger
|
||||
|
||||
DOMAIN = "hypontech"
|
||||
|
||||
CONF_OEM = "oem"
|
||||
DEFAULT_OEM = 0
|
||||
|
||||
LOGGER: Logger = getLogger(__package__)
|
||||
|
||||
@@ -5,6 +5,7 @@ from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
|
||||
from hyponcloud import (
|
||||
KNOWN_OEMS,
|
||||
HyponCloud,
|
||||
OverviewData,
|
||||
PlantData,
|
||||
@@ -16,7 +17,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .const import CONF_OEM, DEFAULT_OEM, DOMAIN, LOGGER
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -37,6 +38,8 @@ class HypontechCoordinatorData:
|
||||
|
||||
type HypontechConfigEntry = ConfigEntry[HypontechDataCoordinator]
|
||||
|
||||
OEM_NAMES = {oem.id: oem.name for oem in KNOWN_OEMS}
|
||||
|
||||
|
||||
class HypontechDataCoordinator(DataUpdateCoordinator[HypontechCoordinatorData]):
|
||||
"""Coordinator used for all sensors."""
|
||||
@@ -60,6 +63,7 @@ class HypontechDataCoordinator(DataUpdateCoordinator[HypontechCoordinatorData]):
|
||||
)
|
||||
self.api = api
|
||||
self.account_id = account_id
|
||||
self.oem_name = OEM_NAMES[int(config_entry.data.get(CONF_OEM, DEFAULT_OEM))]
|
||||
|
||||
async def _async_update_data(self) -> HypontechCoordinatorData:
|
||||
try:
|
||||
|
||||
@@ -18,7 +18,7 @@ class HypontechEntity(CoordinatorEntity[HypontechDataCoordinator]):
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, coordinator.account_id)},
|
||||
name="Overview",
|
||||
manufacturer="Hypontech",
|
||||
manufacturer=coordinator.oem_name,
|
||||
)
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class HypontechPlantEntity(CoordinatorEntity[HypontechDataCoordinator]):
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, plant_id)},
|
||||
name=plant.info.plant_name,
|
||||
manufacturer="Hypontech",
|
||||
manufacturer=coordinator.oem_name,
|
||||
model=plant.info.plant_type,
|
||||
)
|
||||
|
||||
|
||||
@@ -24,10 +24,12 @@
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"oem": "Manufacturer",
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
},
|
||||
"data_description": {
|
||||
"oem": "The brand that sold the equipment",
|
||||
"password": "Your Hypontech Cloud account password.",
|
||||
"username": "Your Hypontech Cloud account username."
|
||||
}
|
||||
|
||||
@@ -59,7 +59,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: IcloudConfigEntry) -> bo
|
||||
await hass.async_add_executor_job(account.setup)
|
||||
|
||||
entry.runtime_data = account
|
||||
entry.async_on_unload(account.cancel_fetch)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
@@ -68,4 +67,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: IcloudConfigEntry) -> bo
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: IcloudConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
await hass.async_add_executor_job(entry.runtime_data.cancel_fetch)
|
||||
return unload_ok
|
||||
|
||||
@@ -248,7 +248,10 @@ def _generate_thumbnail_if_file_does_not_exist(
|
||||
if not target_file.is_file():
|
||||
image = ImageOps.exif_transpose(Image.open(original_path))
|
||||
image.thumbnail(target_size)
|
||||
image.save(target_path, format=content_type.partition("/")[-1])
|
||||
save_format = content_type.partition("/")[-1]
|
||||
if save_format == "jpeg" and image.mode not in ("RGB", "L", "CMYK"):
|
||||
image = image.convert("RGB")
|
||||
image.save(target_path, format=save_format)
|
||||
|
||||
|
||||
def _validate_size_from_filename(filename: str) -> tuple[int, int]:
|
||||
|
||||
@@ -119,7 +119,7 @@ class ImmichDataUpdateCoordinator(DataUpdateCoordinator[ImmichData]):
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_error",
|
||||
translation_placeholders={"error": repr(err)},
|
||||
translation_placeholders={"error": str(err)},
|
||||
) from err
|
||||
|
||||
return ImmichData(
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pyimouapi==1.2.7"]
|
||||
"requirements": ["pyimouapi==1.2.8"]
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ from indevolt_api import (
|
||||
IndevoltConfig,
|
||||
IndevoltEnergyMode,
|
||||
IndevoltRealtimeAction,
|
||||
IndevoltRealtimeState,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -109,6 +110,10 @@ class IndevoltCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
"""Push/write data values to given key on the device."""
|
||||
return await self.api.set_data(sensor_key, value)
|
||||
|
||||
def async_optimistic_update(self, read_key: str, value: Any) -> None:
|
||||
"""Optimistically update coordinator data without fetching from device."""
|
||||
self.async_set_updated_data({**self.data, read_key: value})
|
||||
|
||||
async def async_switch_energy_mode(
|
||||
self, target_mode: IndevoltEnergyMode, refresh: bool = True
|
||||
) -> None:
|
||||
@@ -142,7 +147,9 @@ class IndevoltCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
)
|
||||
|
||||
if refresh:
|
||||
await self.async_request_refresh()
|
||||
self.async_optimistic_update(
|
||||
IndevoltConfig.READ_ENERGY_MODE, target_mode
|
||||
)
|
||||
|
||||
async def async_realtime_action(
|
||||
self,
|
||||
@@ -161,10 +168,15 @@ class IndevoltCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
match action:
|
||||
case IndevoltRealtimeAction.CHARGE:
|
||||
success = await self.api.charge(power, target_soc)
|
||||
state = IndevoltRealtimeState.CHARGING
|
||||
|
||||
case IndevoltRealtimeAction.DISCHARGE:
|
||||
success = await self.api.discharge(power, target_soc)
|
||||
state = IndevoltRealtimeState.DISCHARGING
|
||||
|
||||
case IndevoltRealtimeAction.STOP:
|
||||
success = await self.api.stop()
|
||||
state = IndevoltRealtimeState.STANDBY
|
||||
|
||||
if not success:
|
||||
raise HomeAssistantError(
|
||||
@@ -172,7 +184,15 @@ class IndevoltCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
translation_key="failed_to_execute_realtime_action",
|
||||
)
|
||||
|
||||
await self.async_request_refresh()
|
||||
self.async_set_updated_data(
|
||||
{
|
||||
**self.data,
|
||||
IndevoltConfig.READ_ENERGY_MODE: IndevoltEnergyMode.REAL_TIME_CONTROL,
|
||||
IndevoltConfig.READ_REALTIME_STATE: state,
|
||||
IndevoltConfig.READ_REALTIME_TARGET_SOC: target_soc,
|
||||
IndevoltConfig.READ_REALTIME_POWER_LIMIT: power,
|
||||
}
|
||||
)
|
||||
|
||||
def get_emergency_soc(self) -> int:
|
||||
"""Get the emergency SOC value."""
|
||||
|
||||
@@ -136,7 +136,9 @@ class IndevoltNumberEntity(IndevoltEntity, NumberEntity):
|
||||
)
|
||||
|
||||
if success:
|
||||
await self.coordinator.async_request_refresh()
|
||||
self.coordinator.async_optimistic_update(
|
||||
self.entity_description.read_key, int_value
|
||||
)
|
||||
|
||||
else:
|
||||
raise HomeAssistantError(
|
||||
|
||||
@@ -106,7 +106,9 @@ class IndevoltSelectEntity(IndevoltEntity, SelectEntity):
|
||||
)
|
||||
|
||||
if success:
|
||||
await self.coordinator.async_request_refresh()
|
||||
self.coordinator.async_optimistic_update(
|
||||
self.entity_description.read_key, value
|
||||
)
|
||||
|
||||
else:
|
||||
raise HomeAssistantError(
|
||||
|
||||
@@ -86,7 +86,7 @@ SENSORS: Final = (
|
||||
),
|
||||
# Real-time control state
|
||||
IndevoltSensorEntityDescription(
|
||||
key=IndevoltConfig.READ_REALTIME_COMMAND,
|
||||
key=IndevoltConfig.READ_REALTIME_STATE,
|
||||
translation_key="realtime_command",
|
||||
state_mapping={1000: "standby", 1001: "charging", 1002: "discharging"},
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
|
||||
@@ -126,7 +126,14 @@ class IndevoltSwitchEntity(IndevoltEntity, SwitchEntity):
|
||||
)
|
||||
|
||||
if success:
|
||||
await self.coordinator.async_request_refresh()
|
||||
read_value = (
|
||||
self.entity_description.read_on_value
|
||||
if value
|
||||
else self.entity_description.read_off_value
|
||||
)
|
||||
self.coordinator.async_optimistic_update(
|
||||
self.entity_description.read_key, read_value
|
||||
)
|
||||
|
||||
else:
|
||||
raise HomeAssistantError(
|
||||
|
||||
@@ -21,6 +21,7 @@ from homeassistant.const import (
|
||||
CONF_ICON,
|
||||
CONF_ID,
|
||||
CONF_NAME,
|
||||
CONF_OPTIONS,
|
||||
SERVICE_RELOAD,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
@@ -37,8 +38,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DOMAIN = "input_select"
|
||||
|
||||
CONF_INITIAL = "initial"
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
CONF_OPTIONS = "options"
|
||||
|
||||
SERVICE_SET_OPTIONS = "set_options"
|
||||
STORAGE_KEY = DOMAIN
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["iometer==1.0.1"],
|
||||
"requirements": ["iometer==1.0.2"],
|
||||
"zeroconf": ["_iometer._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["deepmerge", "pyipp"],
|
||||
"requirements": ["pyipp==0.17.0"],
|
||||
"requirements": ["pyipp==0.17.2"],
|
||||
"zeroconf": ["_ipps._tcp.local.", "_ipp._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyiskra"],
|
||||
"requirements": ["pyiskra==0.1.27"]
|
||||
"requirements": ["pyiskra==0.1.29"]
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"location": "[%key:common::config_flow::data::location%]",
|
||||
"name": "[%key:common::config_flow::data::name%]"
|
||||
},
|
||||
"description": "Do you want to set up Islamic Prayer Times?",
|
||||
"title": "Set up Islamic Prayer Times"
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: IsraelRailConfigEntry) -
|
||||
try:
|
||||
await hass.async_add_executor_job(train_schedule.query, start, destination)
|
||||
except Exception as e:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="request_timeout",
|
||||
|
||||
@@ -65,5 +65,10 @@
|
||||
"name": "Trains +2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"request_timeout": {
|
||||
"message": "Timeout connecting to the Israel Rail API for {config_title}: {error}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["pyituran==0.1.5"]
|
||||
"requirements": ["pyituran==0.1.6"]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
|
||||
from jvcprojector import Command, JvcProjector
|
||||
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER, NAME
|
||||
@@ -27,8 +27,12 @@ class JvcProjectorEntity(CoordinatorEntity[JvcProjectorDataUpdateCoordinator]):
|
||||
super().__init__(coordinator, command)
|
||||
|
||||
self._attr_unique_id = coordinator.unique_id
|
||||
# The config entry unique id is the device's formatted MAC address (set
|
||||
# from the projector's MAC in the config flow), so it doubles as the
|
||||
# network MAC connection.
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self._attr_unique_id)},
|
||||
connections={(CONNECTION_NETWORK_MAC, self._attr_unique_id)},
|
||||
name=NAME,
|
||||
model=self.device.model,
|
||||
manufacturer=MANUFACTURER,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""The KNX integration."""
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
@@ -17,11 +18,20 @@ from homeassistant.helpers.storage import STORAGE_DIR
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
CONF_KNX_DEFAULT_RATE_LIMIT,
|
||||
CONF_KNX_DEFAULT_STATE_UPDATER,
|
||||
CONF_KNX_EXPOSE,
|
||||
CONF_KNX_KNXKEY_FILENAME,
|
||||
CONF_KNX_RATE_LIMIT,
|
||||
CONF_KNX_STATE_UPDATER,
|
||||
CONF_KNX_TELEGRAM_DB_LOAD_HOURS,
|
||||
CONF_KNX_TELEGRAM_DB_RETENTION_DAYS,
|
||||
DATA_HASS_CONFIG,
|
||||
DOMAIN,
|
||||
KNX_MODULE_KEY,
|
||||
KNX_TELEGRAM_DB_PATH_SQLITE,
|
||||
KNX_TELEGRAM_DB_RETENTION_DEFAULT,
|
||||
KNX_TELEGRAM_LOAD_HOURS_DEFAULT,
|
||||
SUPPORTED_PLATFORMS_UI,
|
||||
SUPPORTED_PLATFORMS_YAML,
|
||||
)
|
||||
@@ -51,11 +61,12 @@ from .schema import (
|
||||
)
|
||||
from .services import async_setup_services
|
||||
from .storage.config_store import STORAGE_KEY as CONFIG_STORAGE_KEY
|
||||
from .telegrams import STORAGE_KEY as TELEGRAMS_STORAGE_KEY
|
||||
from .websocket import register_panel
|
||||
|
||||
_KNX_YAML_CONFIG: Final = "knx_yaml_config"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.All(
|
||||
@@ -147,6 +158,44 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
)
|
||||
|
||||
await register_panel(hass)
|
||||
return True
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Migrate old entry."""
|
||||
_LOGGER.debug("Migrating from version %s", entry.version)
|
||||
|
||||
if entry.version > 2:
|
||||
# Don't migrate from future version
|
||||
return False
|
||||
|
||||
if entry.version == 1:
|
||||
new_data = {**entry.data}
|
||||
new_options = {**entry.options}
|
||||
new_data.pop("telegram_log_size", None)
|
||||
|
||||
for key in (
|
||||
CONF_KNX_STATE_UPDATER,
|
||||
CONF_KNX_RATE_LIMIT,
|
||||
CONF_KNX_TELEGRAM_DB_LOAD_HOURS,
|
||||
CONF_KNX_TELEGRAM_DB_RETENTION_DAYS,
|
||||
):
|
||||
if key in new_data:
|
||||
new_options[key] = new_data.pop(key)
|
||||
|
||||
new_options.setdefault(
|
||||
CONF_KNX_TELEGRAM_DB_RETENTION_DAYS, KNX_TELEGRAM_DB_RETENTION_DEFAULT
|
||||
)
|
||||
new_options.setdefault(
|
||||
CONF_KNX_TELEGRAM_DB_LOAD_HOURS, KNX_TELEGRAM_LOAD_HOURS_DEFAULT
|
||||
)
|
||||
new_options.setdefault(CONF_KNX_STATE_UPDATER, CONF_KNX_DEFAULT_STATE_UPDATER)
|
||||
new_options.setdefault(CONF_KNX_RATE_LIMIT, CONF_KNX_DEFAULT_RATE_LIMIT)
|
||||
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, data=new_data, options=new_options, version=2
|
||||
)
|
||||
_LOGGER.info("Migration to version 2 successful")
|
||||
|
||||
return True
|
||||
|
||||
@@ -203,7 +252,12 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
(storage_dir / PROJECT_STORAGE_KEY).unlink()
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
(storage_dir / TELEGRAMS_STORAGE_KEY).unlink()
|
||||
(storage_dir / KNX_TELEGRAM_DB_PATH_SQLITE).unlink()
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
(storage_dir / f"{KNX_TELEGRAM_DB_PATH_SQLITE}-wal").unlink()
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
(storage_dir / f"{KNX_TELEGRAM_DB_PATH_SQLITE}-shm").unlink()
|
||||
|
||||
with contextlib.suppress(FileNotFoundError, OSError):
|
||||
(storage_dir / DOMAIN).rmdir()
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ from xknx.io.self_description import request_description
|
||||
from xknx.io.util import validate_ip as xknx_validate_ip
|
||||
from xknx.secure.keyring import Keyring, XMLInterface
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_RECONFIGURE,
|
||||
ConfigEntry,
|
||||
@@ -48,7 +49,8 @@ from .const import (
|
||||
CONF_KNX_SECURE_USER_ID,
|
||||
CONF_KNX_SECURE_USER_PASSWORD,
|
||||
CONF_KNX_STATE_UPDATER,
|
||||
CONF_KNX_TELEGRAM_LOG_SIZE,
|
||||
CONF_KNX_TELEGRAM_DB_LOAD_HOURS,
|
||||
CONF_KNX_TELEGRAM_DB_RETENTION_DAYS,
|
||||
CONF_KNX_TUNNEL_ENDPOINT_IA,
|
||||
CONF_KNX_TUNNELING,
|
||||
CONF_KNX_TUNNELING_TCP,
|
||||
@@ -56,9 +58,10 @@ from .const import (
|
||||
DEFAULT_ROUTING_IA,
|
||||
DOMAIN,
|
||||
KNX_MODULE_KEY,
|
||||
TELEGRAM_LOG_DEFAULT,
|
||||
TELEGRAM_LOG_MAX,
|
||||
KNX_TELEGRAM_DB_RETENTION_DEFAULT,
|
||||
KNX_TELEGRAM_LOAD_HOURS_DEFAULT,
|
||||
KNXConfigEntryData,
|
||||
KNXConfigEntryOptions,
|
||||
)
|
||||
from .storage.keyring import DEFAULT_KNX_KEYRING_FILENAME, save_uploaded_knxkeys_file
|
||||
from .validation import ia_validator, ip_v4_validator
|
||||
@@ -71,14 +74,20 @@ DEFAULT_ENTRY_DATA = KNXConfigEntryData(
|
||||
local_ip=None,
|
||||
multicast_group=DEFAULT_MCAST_GRP,
|
||||
multicast_port=DEFAULT_MCAST_PORT,
|
||||
rate_limit=CONF_KNX_DEFAULT_RATE_LIMIT,
|
||||
route_back=False,
|
||||
)
|
||||
|
||||
DEFAULT_ENTRY_OPTIONS = KNXConfigEntryOptions(
|
||||
rate_limit=CONF_KNX_DEFAULT_RATE_LIMIT,
|
||||
state_updater=CONF_KNX_DEFAULT_STATE_UPDATER,
|
||||
telegram_log_size=TELEGRAM_LOG_DEFAULT,
|
||||
telegram_db_retention_days=KNX_TELEGRAM_DB_RETENTION_DEFAULT,
|
||||
telegram_db_load_hours=KNX_TELEGRAM_LOAD_HOURS_DEFAULT,
|
||||
)
|
||||
|
||||
CONF_KEYRING_FILE: Final = "knxkeys_file"
|
||||
|
||||
CONF_KNX_TELEGRAM_STORE_SECTION: Final = "telegram_store_section"
|
||||
|
||||
CONF_KNX_TUNNELING_TYPE: Final = "tunneling_type"
|
||||
CONF_KNX_TUNNELING_TYPE_LABELS: Final = {
|
||||
CONF_KNX_TUNNELING: "UDP (Tunneling v1)",
|
||||
@@ -103,7 +112,7 @@ _PORT_SELECTOR = vol.All(
|
||||
class KNXConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a KNX config flow."""
|
||||
|
||||
VERSION = 1
|
||||
VERSION = 2
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize KNX config flow."""
|
||||
@@ -184,6 +193,7 @@ class KNXConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return self.async_create_entry(
|
||||
title=title,
|
||||
data=DEFAULT_ENTRY_DATA | self.new_entry_data,
|
||||
options=DEFAULT_ENTRY_OPTIONS,
|
||||
)
|
||||
|
||||
async def async_step_user(
|
||||
@@ -916,17 +926,16 @@ class KNXOptionsFlow(OptionsFlowWithReload):
|
||||
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
"""Initialize KNX options flow."""
|
||||
self.initial_data = dict(config_entry.data)
|
||||
self.initial_options = dict(config_entry.options)
|
||||
self.new_entry_options: KNXConfigEntryOptions = {}
|
||||
|
||||
@callback
|
||||
def finish_flow(self, new_entry_data: KNXConfigEntryData) -> ConfigFlowResult:
|
||||
def finish_flow(self) -> ConfigFlowResult:
|
||||
"""Update the ConfigEntry and finish the flow."""
|
||||
new_data = self.initial_data | new_entry_data
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self.config_entry,
|
||||
data=new_data,
|
||||
return self.async_create_entry(
|
||||
title="",
|
||||
data=self.initial_options | self.new_entry_options,
|
||||
)
|
||||
return self.async_create_entry(title="", data={})
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -939,24 +948,29 @@ class KNXOptionsFlow(OptionsFlowWithReload):
|
||||
) -> ConfigFlowResult:
|
||||
"""Manage KNX communication settings."""
|
||||
if user_input is not None:
|
||||
return self.finish_flow(
|
||||
KNXConfigEntryData(
|
||||
state_updater=user_input[CONF_KNX_STATE_UPDATER],
|
||||
rate_limit=user_input[CONF_KNX_RATE_LIMIT],
|
||||
telegram_log_size=user_input[CONF_KNX_TELEGRAM_LOG_SIZE],
|
||||
)
|
||||
telegram_store_section = user_input[CONF_KNX_TELEGRAM_STORE_SECTION]
|
||||
self.new_entry_options |= KNXConfigEntryOptions(
|
||||
state_updater=user_input[CONF_KNX_STATE_UPDATER],
|
||||
rate_limit=user_input[CONF_KNX_RATE_LIMIT],
|
||||
telegram_db_load_hours=telegram_store_section[
|
||||
CONF_KNX_TELEGRAM_DB_LOAD_HOURS
|
||||
],
|
||||
telegram_db_retention_days=telegram_store_section[
|
||||
CONF_KNX_TELEGRAM_DB_RETENTION_DAYS
|
||||
],
|
||||
)
|
||||
return self.finish_flow()
|
||||
|
||||
data_schema = {
|
||||
vol.Required(
|
||||
CONF_KNX_STATE_UPDATER,
|
||||
default=self.initial_data.get(
|
||||
default=self.initial_options.get(
|
||||
CONF_KNX_STATE_UPDATER, CONF_KNX_DEFAULT_STATE_UPDATER
|
||||
),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Required(
|
||||
CONF_KNX_RATE_LIMIT,
|
||||
default=self.initial_data.get(
|
||||
default=self.initial_options.get(
|
||||
CONF_KNX_RATE_LIMIT, CONF_KNX_DEFAULT_RATE_LIMIT
|
||||
),
|
||||
): vol.All(
|
||||
@@ -969,27 +983,47 @@ class KNXOptionsFlow(OptionsFlowWithReload):
|
||||
),
|
||||
vol.Coerce(int),
|
||||
),
|
||||
vol.Required(
|
||||
CONF_KNX_TELEGRAM_LOG_SIZE,
|
||||
default=self.initial_data.get(
|
||||
CONF_KNX_TELEGRAM_LOG_SIZE, TELEGRAM_LOG_DEFAULT
|
||||
vol.Required(CONF_KNX_TELEGRAM_STORE_SECTION): data_entry_flow.section(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
CONF_KNX_TELEGRAM_DB_LOAD_HOURS,
|
||||
default=self.initial_options.get(
|
||||
CONF_KNX_TELEGRAM_DB_LOAD_HOURS,
|
||||
KNX_TELEGRAM_LOAD_HOURS_DEFAULT,
|
||||
),
|
||||
): vol.All(
|
||||
selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=1,
|
||||
mode=selector.NumberSelectorMode.BOX,
|
||||
unit_of_measurement="h",
|
||||
),
|
||||
),
|
||||
vol.Coerce(int),
|
||||
),
|
||||
vol.Required(
|
||||
CONF_KNX_TELEGRAM_DB_RETENTION_DAYS,
|
||||
default=self.initial_options.get(
|
||||
CONF_KNX_TELEGRAM_DB_RETENTION_DAYS,
|
||||
KNX_TELEGRAM_DB_RETENTION_DEFAULT,
|
||||
),
|
||||
): vol.All(
|
||||
selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=0,
|
||||
mode=selector.NumberSelectorMode.BOX,
|
||||
unit_of_measurement="days",
|
||||
),
|
||||
),
|
||||
vol.Coerce(int),
|
||||
),
|
||||
}
|
||||
),
|
||||
): vol.All(
|
||||
selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=0,
|
||||
max=TELEGRAM_LOG_MAX,
|
||||
mode=selector.NumberSelectorMode.BOX,
|
||||
),
|
||||
),
|
||||
vol.Coerce(int),
|
||||
),
|
||||
}
|
||||
return self.async_show_form(
|
||||
step_id="communication_settings",
|
||||
data_schema=vol.Schema(data_schema),
|
||||
last_step=True,
|
||||
description_placeholders={
|
||||
"telegram_log_size_max": f"{TELEGRAM_LOG_MAX}",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -10,9 +10,11 @@ from xknx.telegram import Telegram
|
||||
from homeassistant.components.climate import FAN_AUTO, FAN_OFF, HVACAction, HVACMode
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
from homeassistant.util.signal_type import SignalType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .knx_module import KNXModule
|
||||
from .telegrams import TelegramDict
|
||||
|
||||
DOMAIN: Final = "knx"
|
||||
KNX_MODULE_KEY: HassKey[KNXModule] = HassKey(DOMAIN)
|
||||
@@ -50,9 +52,18 @@ CONF_KNX_DEFAULT_RATE_LIMIT: Final = 0
|
||||
|
||||
DEFAULT_ROUTING_IA: Final = "0.0.240"
|
||||
|
||||
CONF_KNX_TELEGRAM_LOG_SIZE: Final = "telegram_log_size"
|
||||
TELEGRAM_LOG_DEFAULT: Final = 1000
|
||||
TELEGRAM_LOG_MAX: Final = 25000 # ~10 MB or ~25 hours of reasonable bus load
|
||||
CONF_KNX_TELEGRAM_DB_RETENTION_DAYS: Final = "telegram_db_retention_days"
|
||||
CONF_KNX_TELEGRAM_DB_LOAD_HOURS: Final = "telegram_db_load_hours"
|
||||
|
||||
KNX_TELEGRAM_DB_RETENTION_DEFAULT: Final = 10 # days
|
||||
KNX_TELEGRAM_LOAD_HOURS_DEFAULT: Final = 24 # 1 day
|
||||
KNX_TELEGRAM_DB_PATH_SQLITE: Final = "knx/telegrams.db" # relative to STORAGE_DIR
|
||||
|
||||
# dispatcher signal for KNX interface device triggers
|
||||
SIGNAL_KNX_TELEGRAM: SignalType[Telegram, TelegramDict] = SignalType("knx_telegram")
|
||||
SIGNAL_KNX_DATA_SECURE_ISSUE_TELEGRAM: SignalType[Telegram, TelegramDict] = SignalType(
|
||||
"knx_data_secure_issue_telegram"
|
||||
)
|
||||
|
||||
##
|
||||
# Secure constants
|
||||
@@ -94,10 +105,11 @@ SERVICE_KNX_EXPOSURE_REGISTER: Final = "exposure_register"
|
||||
SERVICE_KNX_READ: Final = "read"
|
||||
|
||||
REPAIR_ISSUE_DATA_SECURE_GROUP_KEY: Final = "data_secure_group_key_issue"
|
||||
REPAIR_ISSUE_TELEGRAM_BACKEND_ERROR: Final = "telegram_backend_error"
|
||||
|
||||
|
||||
class KNXConfigEntryData(TypedDict, total=False):
|
||||
"""Config entry for the KNX integration."""
|
||||
"""Config entry data for the KNX integration."""
|
||||
|
||||
connection_type: str
|
||||
individual_address: str
|
||||
@@ -116,11 +128,16 @@ class KNXConfigEntryData(TypedDict, total=False):
|
||||
knxkeys_password: str # not required
|
||||
backbone_key: str | None # not required
|
||||
sync_latency_tolerance: int | None # not required
|
||||
# OptionsFlow only
|
||||
|
||||
|
||||
class KNXConfigEntryOptions(TypedDict, total=False):
|
||||
"""Config entry options for the KNX integration."""
|
||||
|
||||
state_updater: bool # default state updater: True -> expire 60; False -> init
|
||||
rate_limit: int
|
||||
# Integration only (not forwarded to xknx)
|
||||
telegram_log_size: int # not required
|
||||
telegram_db_retention_days: int
|
||||
telegram_db_load_hours: int
|
||||
|
||||
|
||||
class ColorTempModes(Enum):
|
||||
|
||||
@@ -39,6 +39,9 @@ async def async_get_config_entry_diagnostics(
|
||||
}
|
||||
|
||||
diag["config_entry_data"] = async_redact_data(dict(config_entry.data), TO_REDACT)
|
||||
diag["config_entry_options"] = async_redact_data(
|
||||
dict(config_entry.options), TO_REDACT
|
||||
)
|
||||
|
||||
if proj_info := knx_module.project.info:
|
||||
diag["project_info"] = async_redact_data(proj_info, "name")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Base module for the KNX integration."""
|
||||
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
from xknx import XKNX
|
||||
from xknx.core import XknxConnectionState
|
||||
@@ -43,13 +44,12 @@ from .const import (
|
||||
CONF_KNX_SECURE_USER_ID,
|
||||
CONF_KNX_SECURE_USER_PASSWORD,
|
||||
CONF_KNX_STATE_UPDATER,
|
||||
CONF_KNX_TELEGRAM_LOG_SIZE,
|
||||
CONF_KNX_TUNNEL_ENDPOINT_IA,
|
||||
CONF_KNX_TUNNELING,
|
||||
CONF_KNX_TUNNELING_TCP,
|
||||
CONF_KNX_TUNNELING_TCP_SECURE,
|
||||
KNX_ADDRESS,
|
||||
TELEGRAM_LOG_DEFAULT,
|
||||
KNXConfigEntryOptions,
|
||||
)
|
||||
from .device import KNXInterfaceDevice
|
||||
from .entity import KnxEntityIdentifier
|
||||
@@ -85,7 +85,7 @@ class KNXModule:
|
||||
|
||||
default_state_updater = (
|
||||
TrackerOptions(tracker_type=StateTrackerType.EXPIRE, update_interval_min=60)
|
||||
if self.entry.data[CONF_KNX_STATE_UPDATER]
|
||||
if self.entry.options[CONF_KNX_STATE_UPDATER]
|
||||
else TrackerOptions(
|
||||
tracker_type=StateTrackerType.INIT, update_interval_min=60
|
||||
)
|
||||
@@ -93,7 +93,7 @@ class KNXModule:
|
||||
self.xknx = XKNX(
|
||||
address_format=self.project.get_address_format(),
|
||||
connection_config=self.connection_config(),
|
||||
rate_limit=self.entry.data[CONF_KNX_RATE_LIMIT],
|
||||
rate_limit=self.entry.options[CONF_KNX_RATE_LIMIT],
|
||||
state_updater=default_state_updater,
|
||||
)
|
||||
self.xknx.connection_manager.register_connection_state_changed_cb(
|
||||
@@ -103,7 +103,7 @@ class KNXModule:
|
||||
hass=hass,
|
||||
xknx=self.xknx,
|
||||
project=self.project,
|
||||
log_size=entry.data.get(CONF_KNX_TELEGRAM_LOG_SIZE, TELEGRAM_LOG_DEFAULT),
|
||||
config=cast(KNXConfigEntryOptions, entry.options),
|
||||
)
|
||||
self.interface_device = KNXInterfaceDevice(
|
||||
hass=hass, entry=entry, xknx=self.xknx
|
||||
@@ -131,7 +131,7 @@ class KNXModule:
|
||||
async def stop(self, event: Event | None = None) -> None:
|
||||
"""Stop XKNX object. Disconnect from tunneling or Routing device."""
|
||||
await self.xknx.stop()
|
||||
await self.telegrams.save_history()
|
||||
await self.telegrams.stop()
|
||||
|
||||
def connection_config(self) -> ConnectionConfig:
|
||||
"""Return the connection_config."""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user