You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

367 lines
15 KiB

######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# BearClaw Integration - Home Assistant dispatch + lifecycle callbacks
# Home Assistant dispatches jobs to codex_appliance and receives structured lifecycle callbacks.
# -------------------------------------------------------------------
# Notes: Telegram ingress now lives directly in docker_17/codex_appliance.
# Notes: Most BearClaw decision logic runs in docker_17/codex_appliance (server.js).
# Notes: GitHub capture behavior (issue creation/labels/research flow) belongs in codex_appliance, not HA YAML.
# Notes: Shared script helper `script.joanna_dispatch` lives in config/script/joanna_dispatch.yaml.
# Notes: The callback webhook writes JOANNA activity entries to logbook for traceability and optional HA alerts.
# Notes: Status telemetry polling expects !secret bearclaw_status_url (token header stays !secret bearclaw_token).
# Notes: Nightly Duplicati verification calls a codex_appliance admin endpoint and returns structured health to HA via response_variable.
# Notes: v2 intake is the primary HA contract; legacy command/ingest routes remain appliance-side shims.
# Notes: Command payload supports async_only for automation-first queueing when immediate inline handling is not required.
# Notes: Command payload supports optional metadata for HA dispatch context snapshots.
# Notes: Blog: https://www.vcloudinfo.com/2026/03/joanna-dispatch-telemetry-home-assistant-infrastructure-dashboard/
######################################################################
rest_command:
bearclaw_command:
url: !secret bearclaw_command_url
method: post
timeout: 30
content_type: application/json
headers:
x-codex-token: !secret bearclaw_token
payload: >
{
"kind": "command",
"transport": "ha",
"source": {{ source | default('home_assistant') | tojson }},
"actor": {
"id": {{ user | default('carlo') | tojson }}
},
"conversation": {
"id": {{ source | default('home_assistant') | tojson }},
"type": "automation"
},
"input": {
"text": {{ text | tojson }},
"context": {{ context | default(none) | tojson }},
"callback": {{ callback | default(none) | tojson }}
},
"replyTargets": [
{
"type": "ha",
"callbackEventType": "lifecycle"
}
],
"metadata": {{ metadata | default({}, true) | tojson }},
"priority": {{ priority | default(none) | tojson }},
"async_only": {{ async_only | default(false) | tojson }}
}
bearclaw_ingest:
url: !secret bearclaw_ingest_url
method: post
content_type: application/json
headers:
x-codex-token: !secret bearclaw_token
payload: >
{
"kind": "event",
"transport": "ha",
"source": "homeassistant",
"actor": {
"id": "system"
},
"conversation": {
"id": "homeassistant",
"type": "automation"
},
"input": {
"text": {{ summary | default('event') | tojson }},
"event": {
"summary": {{ summary | default('event') | tojson }},
"wake": {{ wake | default(false) | tojson }},
"priority": {{ priority | default(none) | tojson }},
"source": "homeassistant"
}
},
"replyTargets": []
}
bearclaw_duplicati_verify:
url: !secret bearclaw_duplicati_verify_url
method: post
timeout: 60
content_type: application/json
headers:
x-codex-token: !secret bearclaw_token
payload: >
{
"reason": {{ reason | default('home_assistant') | tojson }}
}
bearclaw_telegram_send:
url: !secret bearclaw_telegram_send_url
method: post
timeout: 30
content_type: application/json
headers:
x-codex-token: !secret bearclaw_token
payload: >
{
"message": {{ message | tojson }},
"parse_mode": {{ parse_mode | default('plain_text') | tojson }},
"disable_web_page_preview": {{ disable_web_page_preview | default(true) | tojson }},
"chat_id": {{ chat_id | default(none) | tojson }},
"user": {{ user | default('carlo') | tojson }}
}
sensor:
- platform: rest
name: BearClaw Status Telemetry
unique_id: bearclaw_status_telemetry
resource: !secret bearclaw_status_url
method: GET
scan_interval: 60
timeout: 30
headers:
x-codex-token: !secret bearclaw_token
value_template: "{{ 'ok' if value_json.ok | default(false) else 'error' }}"
json_attributes:
- dispatchStats
- queue
- active
- platform
- transports
- followups
- qmdHealth
- memoryIndex
template:
- sensor:
- name: Joanna HA Dispatches 24h
unique_id: joanna_ha_dispatches_24h
icon: mdi:calendar-clock
unit_of_measurement: jobs
state_class: measurement
state: >-
{% set stats = state_attr('sensor.bearclaw_status_telemetry', 'dispatchStats') | default({}, true) %}
{{ stats.get('windows', {}).get('h24', {}).get('haAutomation', {}).get('dispatched', 0) | int(0) }}
- name: Joanna HA Dispatches 7d
unique_id: joanna_ha_dispatches_7d
icon: mdi:calendar-range
unit_of_measurement: jobs
state_class: measurement
state: >-
{% set stats = state_attr('sensor.bearclaw_status_telemetry', 'dispatchStats') | default({}, true) %}
{{ stats.get('windows', {}).get('d7', {}).get('haAutomation', {}).get('dispatched', 0) | int(0) }}
- name: Joanna HA Dispatches 30d
unique_id: joanna_ha_dispatches_30d
icon: mdi:calendar-month
unit_of_measurement: jobs
state_class: measurement
state: >-
{% set stats = state_attr('sensor.bearclaw_status_telemetry', 'dispatchStats') | default({}, true) %}
{{ stats.get('windows', {}).get('d30', {}).get('haAutomation', {}).get('dispatched', 0) | int(0) }}
- name: Joanna HA Completed 24h
unique_id: joanna_ha_completed_24h
icon: mdi:check-decagram
unit_of_measurement: jobs
state_class: measurement
state: >-
{% set stats = state_attr('sensor.bearclaw_status_telemetry', 'dispatchStats') | default({}, true) %}
{{ stats.get('windows', {}).get('h24', {}).get('haAutomation', {}).get('completed', 0) | int(0) }}
- name: Joanna HA Success Rate 24h
unique_id: joanna_ha_success_rate_24h
icon: mdi:percent-circle
unit_of_measurement: '%'
state_class: measurement
state: >-
{% set stats = state_attr('sensor.bearclaw_status_telemetry', 'dispatchStats') | default({}, true) %}
{{ stats.get('windows', {}).get('h24', {}).get('haAutomation', {}).get('successRatePct', 0) | float(0) }}
- name: Joanna HA Errors 24h
unique_id: joanna_ha_errors_24h
icon: mdi:alert-circle-outline
unit_of_measurement: jobs
state_class: measurement
state: >-
{% set stats = state_attr('sensor.bearclaw_status_telemetry', 'dispatchStats') | default({}, true) %}
{% set outcomes = stats.get('windows', {}).get('h24', {}).get('haAutomation', {}).get('outcomes', {}) %}
{{ (outcomes.get('error', 0) | int(0)) + (outcomes.get('notify_error', 0) | int(0)) }}
- name: Joanna All Dispatches 24h
unique_id: joanna_all_dispatches_24h
icon: mdi:calendar-clock
unit_of_measurement: jobs
state_class: measurement
state: >-
{% set stats = state_attr('sensor.bearclaw_status_telemetry', 'dispatchStats') | default({}, true) %}
{{ stats.get('windows', {}).get('h24', {}).get('all', {}).get('dispatched', 0) | int(0) }}
- name: Joanna All Dispatches 7d
unique_id: joanna_all_dispatches_7d
icon: mdi:calendar-range
unit_of_measurement: jobs
state_class: measurement
state: >-
{% set stats = state_attr('sensor.bearclaw_status_telemetry', 'dispatchStats') | default({}, true) %}
{{ stats.get('windows', {}).get('d7', {}).get('all', {}).get('dispatched', 0) | int(0) }}
- name: Joanna All Dispatches 30d
unique_id: joanna_all_dispatches_30d
icon: mdi:calendar-month
unit_of_measurement: jobs
state_class: measurement
state: >-
{% set stats = state_attr('sensor.bearclaw_status_telemetry', 'dispatchStats') | default({}, true) %}
{{ stats.get('windows', {}).get('d30', {}).get('all', {}).get('dispatched', 0) | int(0) }}
- name: Joanna Queue Pending
unique_id: joanna_queue_pending
icon: mdi:playlist-clock
unit_of_measurement: jobs
state_class: measurement
state: >-
{% set queue = state_attr('sensor.bearclaw_status_telemetry', 'queue') | default({}, true) %}
{{ queue.get('pending', 0) | int(0) }}
- name: Joanna Active Job Minutes
unique_id: joanna_active_job_minutes
icon: mdi:timer-outline
unit_of_measurement: min
state_class: measurement
state: >-
{% set active = state_attr('sensor.bearclaw_status_telemetry', 'active') | default({}, true) %}
{% set elapsed_ms = active.get('elapsedMs', 0) | int(0) %}
{{ (elapsed_ms / 60000) | round(1) }}
- name: Joanna Minutes Since HA Dispatch
unique_id: joanna_minutes_since_ha_dispatch
icon: mdi:clock-fast
unit_of_measurement: min
state_class: measurement
state: >-
{% set stats = state_attr('sensor.bearclaw_status_telemetry', 'dispatchStats') | default({}, true) %}
{% set dispatched_at = stats.get('latest', {}).get('haAutomation', {}).get('dispatchedAt') %}
{% set dispatched_ts = as_timestamp(dispatched_at, 0) %}
{% if dispatched_ts > 0 %}
{{ ((as_timestamp(now()) - dispatched_ts) / 60) | round(1) }}
{% else %}
0
{% endif %}
- name: Joanna QMD Consecutive Failures
unique_id: joanna_qmd_consecutive_failures
icon: mdi:heart-pulse
unit_of_measurement: checks
state_class: measurement
state: >-
{% set qmd = state_attr('sensor.bearclaw_status_telemetry', 'qmdHealth') | default({}, true) %}
{{ qmd.get('consecutiveFailures', 0) | int(0) }}
- name: Joanna Memory Docs
unique_id: joanna_memory_docs
icon: mdi:file-document-multiple-outline
unit_of_measurement: docs
state_class: measurement
state: >-
{% set memory = state_attr('sensor.bearclaw_status_telemetry', 'memoryIndex') | default({}, true) %}
{{ memory.get('docs', 0) | int(0) }}
- name: Joanna Memory Chunks
unique_id: joanna_memory_chunks
icon: mdi:text-box-search-outline
unit_of_measurement: chunks
state_class: measurement
state: >-
{% set memory = state_attr('sensor.bearclaw_status_telemetry', 'memoryIndex') | default({}, true) %}
{{ memory.get('chunks', 0) | int(0) }}
- name: Joanna Minutes Since Memory Index
unique_id: joanna_minutes_since_memory_index
icon: mdi:database-clock-outline
unit_of_measurement: min
state_class: measurement
state: >-
{% set memory = state_attr('sensor.bearclaw_status_telemetry', 'memoryIndex') | default({}, true) %}
{% set indexed_at = memory.get('indexedAt') %}
{% set indexed_ts = as_timestamp(indexed_at, 0) %}
{% if indexed_ts > 0 %}
{{ ((as_timestamp(now()) - indexed_ts) / 60) | round(1) }}
{% else %}
0
{% endif %}
- binary_sensor:
- name: Joanna QMD Healthy
unique_id: joanna_qmd_healthy
icon: mdi:heart-pulse
state: >-
{% set qmd = state_attr('sensor.bearclaw_status_telemetry', 'qmdHealth') | default({}, true) %}
{{ qmd.get('healthy', false) in [true, 'true', 'True', 'on', 'yes', 1, '1'] }}
- name: Joanna Session Source Indexed
unique_id: joanna_session_source_indexed
icon: mdi:message-text-clock-outline
state: >-
{% set memory = state_attr('sensor.bearclaw_status_telemetry', 'memoryIndex') | default({}, true) %}
{% set options = memory.get('options', {}) if memory is mapping else {} %}
{{ options.get('includeSessionSource', false) in [true, 'true', 'True', 'on', 'yes', 1, '1'] }}
- name: Joanna Memory Index Stale
unique_id: joanna_memory_index_stale
icon: mdi:database-alert-outline
state: >-
{% set memory = state_attr('sensor.bearclaw_status_telemetry', 'memoryIndex') | default({}, true) %}
{{ memory.get('stale', false) in [true, 'true', 'True', 'on', 'yes', 1, '1'] }}
automation:
- id: bearclaw_lifecycle_webhook
alias: BearClaw Lifecycle Webhook
description: Receives structured BearClaw lifecycle callbacks for HA-triggered work.
mode: queued
trigger:
- platform: webhook
webhook_id: !secret bearclaw_reply_webhook_id
allowed_methods:
- POST
local_only: true
action:
- variables:
event_type: "{{ trigger.json.event_type | default('progress') | lower }}"
status: "{{ trigger.json.status | default(event_type, true) | lower }}"
source: "{{ trigger.json.source | default('homeassistant', true) | string | lower | trim }}"
severity: "{{ trigger.json.severity | default('active', true) | string | lower | trim }}"
summary: "{{ trigger.json.summary | default('Joanna lifecycle callback', true) }}"
message: "{{ trigger.json.message | default(summary, true) }}"
job_id: "{{ trigger.json.job_id | default('', true) }}"
run_id: "{{ trigger.json.run_id | default('', true) }}"
logbook_message: >-
{% set compact = (event_type ~ ' | ' ~ summary ~ ' | ' ~ message) | replace('\r', ' ') | replace('\n', ' ') | trim %}
{% if compact | length > 240 %}
{{ compact[:237] ~ '...' }}
{% else %}
{{ compact }}
{% endif %}
- service: script.send_to_logbook
data:
topic: JOANNA
message: "{{ status | upper }}: {{ logbook_message }}"
- choose:
- conditions:
- condition: template
value_template: "{{ severity in ['warning', 'error', 'critical'] or status in ['failed', 'canceled'] }}"
sequence:
- service: script.notify_engine
data:
title: Joanna Alert
value1: >-
{{ summary }}
{% if job_id | trim != '' %}
(job={{ job_id }})
{% endif %}
{% if run_id | trim != '' %}
run={{ run_id }}
{% endif %}
{{ '\n' ~ message if message | trim != '' else '' }}

Powered by TurnKey Linux.