Add Codex reset credit telemetry

This commit is contained in:
Carlo Costanzo
2026-06-24 17:05:47 -04:00
parent c7221fd53b
commit 23c85be70b
4 changed files with 274 additions and 2 deletions
@@ -34,6 +34,16 @@
entity: sensor.joanna_ha_errors_24h
name: HA Errors 24H
icon: mdi:alert-circle-outline
- type: custom:button-card
template: bearstone_infra_kpi
entity: sensor.codex_reset_credits_available
name: Resets
icon: mdi:backup-restore
- type: custom:button-card
template: bearstone_infra_kpi
entity: sensor.codex_reset_credits_expiring_soon
name: Reset Warnings
icon: mdi:alert-clock
- type: custom:button-card
template: bearstone_infra_kpi
entity: sensor.joanna_all_dispatches_24h
@@ -174,6 +184,114 @@
return `${enabled ? 'enabled' : 'disabled'} | ${age} min ago`;
]]]
- type: grid
column_span: 4
columns: 1
square: false
cards:
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Codex Reset Credits
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.codex_reset_credit_status
name: Collector Status
icon: mdi:clipboard-pulse-outline
state_display: >
[[[
const usage = states['sensor.bearclaw_status_telemetry']?.attributes?.codexUsage || {};
const status = usage.status || 'unknown';
const ok = usage.ok === true;
const last = usage.lastCheckAt ? String(usage.lastCheckAt).replace('T', ' ').slice(0, 16) : 'not checked';
return `${ok ? 'ok' : status} | ${last}`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.codex_reset_credit_next_expiration
name: Next Expiration
icon: mdi:calendar-clock
state_display: >
[[[
const usage = states['sensor.bearclaw_status_telemetry']?.attributes?.codexUsage || {};
const days = usage.minDaysRemaining;
const expRaw = usage.soonestExpiresAt || '';
const exp = expRaw ? String(expRaw).replace('T', ' ').slice(0, 16) : 'n/a';
const dayText = Number.isFinite(Number(days)) ? `${Number(days).toFixed(1)}d` : 'n/a';
return `${dayText} | ${exp}`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: binary_sensor.codex_reset_credit_expiring_soon
name: Repair Window
icon: mdi:alert-clock
state_display: >
[[[
const usage = states['sensor.bearclaw_status_telemetry']?.attributes?.codexUsage || {};
const warn = usage.warnDays ?? 7;
const count = usage.warningCount ?? 0;
return `${count} within ${warn}d`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.bearclaw_status_telemetry
name: Reset #1
icon: mdi:backup-restore
state_display: >
[[[
const credits = states['sensor.bearclaw_status_telemetry']?.attributes?.codexUsage?.credits || [];
const credit = credits[0];
if (!credit) return 'No reset';
const days = Number.isFinite(Number(credit.days_remaining)) ? `${Number(credit.days_remaining).toFixed(1)}d` : 'n/a';
const exp = credit.expires_at ? String(credit.expires_at).replace('T', ' ').slice(0, 16) : 'n/a';
return `${credit.status || 'unknown'} | ${days} | ${exp}`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.bearclaw_status_telemetry
name: Reset #2
icon: mdi:backup-restore
state_display: >
[[[
const credits = states['sensor.bearclaw_status_telemetry']?.attributes?.codexUsage?.credits || [];
const credit = credits[1];
if (!credit) return 'No reset';
const days = Number.isFinite(Number(credit.days_remaining)) ? `${Number(credit.days_remaining).toFixed(1)}d` : 'n/a';
const exp = credit.expires_at ? String(credit.expires_at).replace('T', ' ').slice(0, 16) : 'n/a';
return `${credit.status || 'unknown'} | ${days} | ${exp}`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.bearclaw_status_telemetry
name: Reset #3
icon: mdi:backup-restore
state_display: >
[[[
const credits = states['sensor.bearclaw_status_telemetry']?.attributes?.codexUsage?.credits || [];
const credit = credits[2];
if (!credit) return 'No reset';
const days = Number.isFinite(Number(credit.days_remaining)) ? `${Number(credit.days_remaining).toFixed(1)}d` : 'n/a';
const exp = credit.expires_at ? String(credit.expires_at).replace('T', ' ').slice(0, 16) : 'n/a';
return `${credit.status || 'unknown'} | ${days} | ${exp}`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.bearclaw_status_telemetry
name: Reset #4
icon: mdi:backup-restore
state_display: >
[[[
const credits = states['sensor.bearclaw_status_telemetry']?.attributes?.codexUsage?.credits || [];
const credit = credits[3];
if (!credit) return 'No reset';
const days = Number.isFinite(Number(credit.days_remaining)) ? `${Number(credit.days_remaining).toFixed(1)}d` : 'n/a';
const exp = credit.expires_at ? String(credit.expires_at).replace('T', ' ').slice(0, 16) : 'n/a';
return `${credit.status || 'unknown'} | ${days} | ${exp}`;
]]]
- type: grid
column_span: 4
columns: 1
@@ -204,5 +322,7 @@
hours_to_show: 72
entities:
- automation.bearclaw_reply_webhook
- automation.codex_reset_credit_expiry_repair
- automation.tugtainer_dispatch_joanna_for_available_updates
- binary_sensor.codex_reset_credit_expiring_soon
- script.joanna_dispatch
@@ -7,6 +7,7 @@
# Dispatch and activity telemetry for BearClaw/Joanna.
# -------------------------------------------------------------------
# Notes: Uses telemetry from /api/bearclaw/status ingested by bearclaw.yaml.
# Notes: Shows Codex reset-credit expiry data from the codexUsage status field.
######################################################################
type: sections
+2 -2
View File
@@ -50,7 +50,7 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this
| [mariadb_monitoring.yaml](mariadb_monitoring.yaml) | MariaDB health sensors and Lovelace dashboard snippet for recorder stats. | `sensor.mariadb_status`, `sensor.database_size` |
| [llmvision.yaml](llmvision.yaml) | Vision-backed garage-can and front-door package checks with rate-limited, downscaled OpenAI calls for package detection; see the [package reminder video](https://youtu.be/nAhCezFetvI). | `input_button.llmvision_*`, `binary_sensor.front_door_packages_present`, `llmvision.stream_analyzer` |
| [docker_infrastructure.yaml](docker_infrastructure.yaml) | Docker host patching telemetry, container/stack Repairs automation, retired Portainer repair cleanup, 20-minute Joanna escalation for persistent container outages using stable configured monitor membership, and weekly scheduled prune actions across docker_10/14/17/69; the dedicated codex_appliance VM is monitored through BearClaw status telemetry. | `sensor.docker_*_apt_status`, `binary_sensor.*_stack_status`, `sensor.docker_stacks_down_count`, `repairs.create`, `repairs.remove`, `script.joanna_dispatch` |
| [proxmox.yaml](proxmox.yaml) | Proxmox runtime and disk pressure monitoring with Repairs + Joanna dispatch for sustained node degradations, plus nightly Frigate reboot. | `binary_sensor.proxmox*_runtime_healthy`, `sensor.proxmox*_disk_used_percentage`, `repairs.create`, `script.joanna_dispatch`, `button.qemu_docker2_101_reboot` |
| [proxmox.yaml](proxmox.yaml) | Proxmox update detection with HA notifications, Repairs + Joanna upgrade orchestration, kernel-refresh handoff hints, runtime and disk pressure monitoring, plus nightly Frigate reboot. | `binary_sensor.node_proxmox*_updates_packages`, `sensor.node_proxmox*_total_updates`, `persistent_notification.create`, `script.joanna_dispatch`, `binary_sensor.proxmox*_runtime_healthy`, `sensor.proxmox*_disk_used_percentage`, `button.qemu_docker2_101_reboot` |
| [synology_dsm.yaml](synology_dsm.yaml) | Synology DSM integration health normalization for Carlo-NAS01 and Carlo-NVR, with outage-aware Joanna-first handling for lone post-outage volume warnings and Repairs escalation for persistent or non-outage problems. | `binary_sensor.carlo_*_synology_problem`, `sensor.carlo_*_synology_problem_summary`, `binary_sensor.powerwall_grid_status`, `repairs.create`, `script.joanna_dispatch` |
| [infrastructure.yaml](infrastructure.yaml) | Normalized WAN/DNS/backup/domain/cert health, Nebula Sync and promoted IoT primary/backup Pi-hole consistency monitoring with Joanna dispatch, Glances-backed Docker host disk pressure with Joanna-only warning cleanup and critical Repairs, and website uptime/latency SLO signals for Infrastructure dashboards, plus nightly backup verification and monthly Joanna HA log hygiene review with GitHub issue follow-up. | `sensor.infra_nebula_sync_dns_consistency`, `sensor.infra_pihole_iot_dns_consistency`, `binary_sensor.infra_nebula_sync_degraded`, `binary_sensor.infra_pihole_iot_dns_degraded`, `sensor.docker_*_disk_used_percentage`, `automation.infra_nebula_sync_health_dispatch`, `automation.infra_pihole_iot_dns_drift_dispatch`, `automation.docker_host_disk_pressure_monitor`, `binary_sensor.infra_website_uptime_slo_breach`, `binary_sensor.infra_website_latency_degraded`, `automation.infra_backup_nightly_verification`, `script.joanna_dispatch` |
| [onenote_indexer.yaml](onenote_indexer.yaml) | Dedicated-appliance OneNote indexer health/status monitoring for Joanna, explicit index-health confirmation, failure-repair automation, and a daily duplicate-delete maintenance request. | `sensor.onenote_indexer_last_job_status`, `binary_sensor.onenote_indexer_last_job_successful`, `binary_sensor.onenote_indexer_index_healthy` |
@@ -59,7 +59,7 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this
| [processmonitor.yaml](processmonitor.yaml) | Root filesystem disk-pressure monitoring with immediate digest/logbook notes at 80%, Joanna review after 10 minutes above 80%, and delayed phone alerts only if the issue stays unresolved after dispatch. | `sensor.disk_use_percent`, `repairs.create`, `script.joanna_dispatch`, `tts.clear_cache` |
| [tugtainer_updates.yaml](tugtainer_updates.yaml) | Tugtainer container update notifications via webhook + persistent alerts, plus event-based Joanna dispatch when reports include `### Available:` (24h cooldown via `mode: single` + delay, no new helpers). | `persistent_notification.create`, `event: tugtainer_available_detected`, `script.joanna_dispatch`, `input_datetime.tugtainer_last_update` |
| [printer.yaml](printer.yaml) | Epson ink watchdog with one-day and one-week mobile snooze actions for low-ink reminders. | `input_datetime.printer_ink_snooze_until`, `sensor.epson_*`, mobile app action events |
| [bearclaw.yaml](bearclaw.yaml) | Joanna/BearClaw bridge automations that forward Telegram commands to codex_appliance, include LLM-first routing context for freeform text, relay replies back, ingest `/api/bearclaw/status` telemetry, and expose dispatch plus QMD/memory-index sensors for Infrastructure dashboards. | `rest_command.bearclaw_*`, `sensor.bearclaw_status_telemetry`, `sensor.joanna_*`, `binary_sensor.joanna_*`, `automation.bearclaw_*`, `script.send_to_logbook` |
| [bearclaw.yaml](bearclaw.yaml) | Joanna/BearClaw bridge automations that forward Telegram commands to codex_appliance, include LLM-first routing context for freeform text, relay replies back, ingest `/api/bearclaw/status` telemetry, and expose dispatch, QMD/memory-index, plus Codex reset-credit expiry sensors and Repairs for Infrastructure dashboards. | `rest_command.bearclaw_*`, `sensor.bearclaw_status_telemetry`, `sensor.joanna_*`, `sensor.codex_reset_*`, `binary_sensor.joanna_*`, `binary_sensor.codex_reset_credit_expiring_soon`, `automation.bearclaw_*`, `automation.codex_reset_credit_expiry_repair`, `repairs.create`, `repairs.remove`, `script.send_to_logbook` |
| [telegram_bot.yaml](telegram_bot.yaml) | Legacy Telegram transport marker for BearClaw; the shared `joanna_send_telegram` helper now forwards through the codex_appliance direct Telegram API. | `rest_command.bearclaw_telegram_send`, `script.joanna_send_telegram` |
| [phynplus.yaml](phynplus.yaml) | Phyn shutoff automations with leak-test guard, Activity Feed context, Repairs tracking, and critical push recovery when the valve closes; see the [leak detection automation video](https://youtu.be/xbhgWnomFYI). | `valve.phyn_shutoff_valve`, `binary_sensor.phyn_leak_test_running`, `script.phyn_send_actionable_leak_notification`, `repairs.create` |
| [water_delivery.yaml](water_delivery.yaml) | ReadyRefresh delivery date helper with night-before + garage door Alexa reminders, plus helper-change audit logging and Telegram confirmations. | `input_datetime.water_delivery_date`, `script.send_to_logbook`, `script.joanna_send_telegram`, `notify.alexa_media_garage` |
+151
View File
@@ -6,12 +6,14 @@
# BearClaw Integration - Home Assistant dispatch + lifecycle callbacks
# Home Assistant dispatches jobs to codex_appliance and receives structured lifecycle callbacks.
# -------------------------------------------------------------------
# Related Issue: 1797
# Notes: Telegram ingress now lives directly in the dedicated codex_appliance VM.
# Notes: Most BearClaw decision logic runs in 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: Codex reset-credit expiry telemetry is sourced from the appliance `codexUsage` status field.
# 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.
@@ -135,6 +137,7 @@ sensor:
- platform
- transports
- followups
- codexUsage
- qmdHealth
- memoryIndex
@@ -241,6 +244,70 @@ template:
{% set elapsed_ms = active.get('elapsedMs', 0) | int(0) %}
{{ (elapsed_ms / 60000) | round(1) }}
- name: Codex Reset Credits Available
unique_id: codex_reset_credits_available
icon: mdi:backup-restore
unit_of_measurement: resets
state_class: measurement
state: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('availableCount', 0) | int(0) }}
- name: Codex Reset Credits Expiring Soon
unique_id: codex_reset_credits_expiring_soon
icon: mdi:alert-clock
unit_of_measurement: resets
state_class: measurement
state: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('warningCount', 0) | int(0) }}
- name: Codex Reset Credit Min Days Remaining
unique_id: codex_reset_credit_min_days_remaining
icon: mdi:timer-sand
unit_of_measurement: d
state_class: measurement
availability: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('minDaysRemaining') not in [none, 'unknown', 'unavailable', ''] }}
state: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('minDaysRemaining', 0) | float(0) | round(1) }}
- name: Codex Reset Credit Next Expiration
unique_id: codex_reset_credit_next_expiration
icon: mdi:calendar-clock
device_class: timestamp
availability: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('soonestExpiresAt') not in [none, 'unknown', 'unavailable', ''] }}
state: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('soonestExpiresAt') }}
- name: Codex Reset Credit Status
unique_id: codex_reset_credit_status
icon: mdi:clipboard-pulse-outline
state: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('status', 'unknown') }}
attributes:
ok: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('ok', false) }}
last_check_at: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('lastCheckAt') }}
next_check_at: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('nextCheckAt') }}
last_error: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('lastError') }}
credits: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('credits', []) }}
- name: Joanna Minutes Since HA Dispatch
unique_id: joanna_minutes_since_ha_dispatch
icon: mdi:clock-fast
@@ -320,7 +387,91 @@ template:
state: >-
{% set memory = state_attr('sensor.bearclaw_status_telemetry', 'memoryIndex') | default({}, true) %}
{{ memory.get('stale', false) in [true, 'true', 'True', 'on', 'yes', 1, '1'] }}
- name: Codex Reset Credit Expiring Soon
unique_id: codex_reset_credit_expiring_soon
icon: mdi:alert-clock
state: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('issueActive', false) in [true, 'true', 'True', 'on', 'yes', 1, '1'] }}
attributes:
warn_days: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('warnDays', 7) }}
warning_count: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('warningCount', 0) }}
soonest_expires_at: >-
{% set usage = state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) %}
{{ usage.get('soonestExpiresAt') }}
automation:
- id: codex_reset_credit_expiry_repair
alias: Codex Reset Credit Expiry Repair
description: "Create/clear a Repair while available Codex reset credits are within the expiry warning window."
mode: single
trigger:
- platform: state
entity_id: binary_sensor.codex_reset_credit_expiring_soon
- platform: time_pattern
hours: "/6"
variables:
usage: "{{ state_attr('sensor.bearclaw_status_telemetry', 'codexUsage') | default({}, true) }}"
usage_ok: "{{ usage.get('ok', false) in [true, 'true', 'True', 'on', 'yes', 1, '1'] }}"
issue_active: "{{ is_state('binary_sensor.codex_reset_credit_expiring_soon', 'on') }}"
warn_days: "{{ usage.get('warnDays', 7) | int(7) }}"
warning_count: "{{ usage.get('warningCount', 0) | int(0) }}"
min_days: "{{ usage.get('minDaysRemaining') }}"
soonest_expires_at: "{{ usage.get('soonestExpiresAt') | default('unknown', true) }}"
last_check_at: "{{ usage.get('lastCheckAt') | default('unknown', true) }}"
expiring_summary: >-
{% set credits = usage.get('credits', []) %}
{% set ns = namespace(lines=[]) %}
{% for credit in credits %}
{% set status = credit.get('status', 'unknown') %}
{% set days = credit.get('days_remaining') %}
{% if status == 'available' and days is number and days <= warn_days %}
{% set ns.lines = ns.lines + [
'Reset #' ~ credit.get('index', loop.index) ~ ': ' ~
(days | round(1)) ~ ' days remaining; expires ' ~
credit.get('expires_at', 'unknown')
] %}
{% endif %}
{% endfor %}
{{ ns.lines | join('\n') if ns.lines | count > 0 else 'No reset credits are inside the warning window.' }}
action:
- choose:
- conditions: "{{ issue_active }}"
sequence:
- service: repairs.create
data:
issue_id: codex_reset_credit_expiring_soon
title: "Codex reset credit expiring soon"
description: >-
{{ warning_count }} available Codex reset credit(s) expire within {{ warn_days }} days.
Soonest expiration: {{ soonest_expires_at }}
Minimum days remaining: {{ min_days }}
Last successful/attempted check: {{ last_check_at }}
{{ expiring_summary }}
severity: warning
persistent: true
- service: script.send_to_logbook
data:
topic: "CODEX"
message: >-
Codex reset credit warning active: {{ warning_count }} reset(s) within {{ warn_days }} days.
- conditions: "{{ usage_ok }}"
sequence:
- service: repairs.remove
continue_on_error: true
data:
issue_id: codex_reset_credit_expiring_soon
- service: script.send_to_logbook
data:
topic: "CODEX"
message: "Codex reset credit warning cleared after a successful reset snapshot."
- id: bearclaw_lifecycle_webhook
alias: BearClaw Lifecycle Webhook
description: Receives structured BearClaw lifecycle callbacks for HA-triggered work.