From 19eb432b2b2f9d18aaf4e8f887b2c88507c92d20 Mon Sep 17 00:00:00 2001 From: NOVA Date: Tue, 7 Jul 2026 15:30:56 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20use=20.connected/.running=20from=20healt?= =?UTF-8?q?h=20output=20=E2=80=94=20closes=20#35?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit openclaw health --json now exposes channel state via .connected and .running rather than .probe.ok. Update update_system() to map connected -> online, running -> idle, otherwise offline (with legacy .probe.ok fallback). Also update bot/team/latency field paths to the new channel-level structure while preserving legacy .probe.* fallbacks. Added redacted health fixture and test for the mapping behavior (including legacy probe fallback). --- scripts/update-dashboard.sh | 21 +++-- tests/fixtures/health-sample.json | 54 ++++++++++++ tests/test-issue-35.sh | 134 ++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 7 deletions(-) create mode 100644 tests/fixtures/health-sample.json create mode 100755 tests/test-issue-35.sh diff --git a/scripts/update-dashboard.sh b/scripts/update-dashboard.sh index 663ef93..15ba6d2 100755 --- a/scripts/update-dashboard.sh +++ b/scripts/update-dashboard.sh @@ -112,21 +112,28 @@ update_system() { if echo "$health_json" | jq empty 2>/dev/null; then gateway_status="running" - # Build channels object from probe data. - # openclaw health --json returns: - # { channels: { : { probe: { ok, elapsedMs, bot, team } } } } + # Build channels object from health data. + # openclaw health --json returns (v2026.4+): + # { channels: { : { connected, running, bot: { username }, team: { name } } } } # We map each channel to: { status, latencyMs?, bot?, team? } - # Null fields are stripped with_entries(select(.value != null)) for cleaner JSON. + # Status: connected -> online, running -> idle, otherwise offline. + # Legacy .probe.ok is still honored as a fallback. channels_json=$(echo "$health_json" | jq -c ' .channels // {} | to_entries | map({ key: .key, value: ( .value | { - status: (if (.probe.ok // false) then "online" else "offline" end), + status: ( + if (.connected // false) then "online" + elif (.running // false) then "idle" + elif (.probe.ok // false) then "online" + else "offline" + end + ), latencyMs: (.probe.elapsedMs // null), - bot: (.probe.bot.username // .probe.bot.name // null), - team: (if (.probe.team.name // null) != null then .probe.team.name else null end) + bot: (.bot.username // .bot.name // .probe.bot.username // .probe.bot.name // null), + team: (.team.name // .probe.team.name // null) } | # Remove null fields so the JSON stays lean with_entries(select(.value != null)) diff --git a/tests/fixtures/health-sample.json b/tests/fixtures/health-sample.json new file mode 100644 index 0000000..0827254 --- /dev/null +++ b/tests/fixtures/health-sample.json @@ -0,0 +1,54 @@ +{ + "ok": true, + "ts": 1783437860428, + "durationMs": 99, + "channels": { + "discord": { + "enabled": true, + "configured": true, + "running": true, + "connected": true, + "lastError": null, + "bot": { + "id": "REDACTED", + "username": "NOVA" + }, + "team": null + }, + "slack": { + "enabled": true, + "configured": true, + "running": true, + "connected": true, + "lastError": null + }, + "telegram": { + "enabled": true, + "configured": true, + "running": false, + "connected": true, + "lastError": null + }, + "signal": { + "enabled": true, + "configured": true, + "running": true, + "connected": null, + "lastError": null + }, + "agent_chat": { + "enabled": true, + "configured": true, + "running": true, + "connected": null, + "lastError": null + }, + "offline_channel": { + "enabled": true, + "configured": true, + "running": false, + "connected": false, + "lastError": "disconnected" + } + } +} diff --git a/tests/test-issue-35.sh b/tests/test-issue-35.sh new file mode 100755 index 0000000..57ea2c2 --- /dev/null +++ b/tests/test-issue-35.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# Test fixture for issue #35 — channel status mapping from health output. +# +# Validates that the jq transform used by scripts/update-dashboard.sh maps the +# current `openclaw health --json` schema to the dashboard channel model: +# connected=true -> "online" +# connected=false/null, running=true -> "idle" +# otherwise -> "offline" +# Bot and team fields are read from .bot.username / .team.name, and legacy +# .probe.ok paths still fall back correctly. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FIXTURE="${SCRIPT_DIR}/fixtures/health-sample.json" + +PASS=0 +FAIL=0 + +pass() { echo " ✅ PASS: $1"; PASS=$((PASS+1)); } +fail() { echo " ❌ FAIL: $1"; FAIL=$((FAIL+1)); } + +# The same channel transform used by scripts/update-dashboard.sh. +CHANNEL_JQ=' + .channels // {} | to_entries | map({ + key: .key, + value: ( + .value | + { + status: ( + if (.connected // false) then "online" + elif (.running // false) then "idle" + elif (.probe.ok // false) then "online" + else "offline" + end + ), + latencyMs: (.probe.elapsedMs // null), + bot: (.bot.username // .bot.name // .probe.bot.username // .probe.bot.name // null), + team: (.team.name // .probe.team.name // null) + } | + with_entries(select(.value != null)) + ) + }) | from_entries +' + +echo "" +echo "=== Issue #35: health output channel mapping ===" +echo "Fixture: $FIXTURE" +echo "" + +# Apply the channel transform to the fixture. +CHANNELS_JSON=$(jq -c "$CHANNEL_JQ" "$FIXTURE") + +echo "[ Channel status mapping ]" + +check_status() { + local channel="$1" + local expected="$2" + local actual + actual=$(echo "$CHANNELS_JSON" | jq -r --arg ch "$channel" '.[$ch].status // empty') + if [ "$actual" = "$expected" ]; then + pass "${channel} -> ${expected}" + else + fail "${channel} expected ${expected}, got '${actual}'" + fi +} + +check_status "discord" "online" +check_status "slack" "online" +check_status "telegram" "online" +check_status "signal" "idle" +check_status "agent_chat" "idle" +check_status "offline_channel" "offline" + +echo "" +echo "[ Bot / team field paths ]" + +DISCORD_BOT=$(echo "$CHANNELS_JSON" | jq -r '.discord.bot // empty') +if [ "$DISCORD_BOT" = "NOVA" ]; then + pass "discord bot name parsed from .bot.username" +else + fail "discord bot name expected 'NOVA', got '${DISCORD_BOT}'" +fi + +if echo "$CHANNELS_JSON" | jq -e '.discord | has("team")' >/dev/null 2>&1; then + fail "discord team should be omitted when unavailable" +else + pass "discord team omitted when unavailable" +fi + +if echo "$CHANNELS_JSON" | jq -e '.discord | has("latencyMs")' >/dev/null 2>&1; then + fail "discord latencyMs should be omitted when unavailable" +else + pass "discord latencyMs omitted when unavailable" +fi + +echo "" +echo "[ Legacy .probe.ok fallback ]" +LEGACY_JSON='{"channels":{"legacy":{"probe":{"ok":true,"elapsedMs":42,"bot":{"username":"oldbot"},"team":{"name":"oldteam"}}}}}' +LEGACY_OUT=$(echo "$LEGACY_JSON" | jq -c "$CHANNEL_JQ") +if [ "$(echo "$LEGACY_OUT" | jq -r '.legacy.status')" = "online" ]; then + pass "legacy .probe.ok still maps to online" +else + fail "legacy .probe.ok did not map to online" +fi +if [ "$(echo "$LEGACY_OUT" | jq -r '.legacy.bot')" = "oldbot" ]; then + pass "legacy .probe.bot.username still parsed" +else + fail "legacy .probe.bot.username not parsed" +fi +if [ "$(echo "$LEGACY_OUT" | jq -r '.legacy.team')" = "oldteam" ]; then + pass "legacy .probe.team.name still parsed" +else + fail "legacy .probe.team.name not parsed" +fi +if [ "$(echo "$LEGACY_OUT" | jq -r '.legacy.latencyMs')" = "42" ]; then + pass "legacy .probe.elapsedMs still parsed" +else + fail "legacy .probe.elapsedMs not parsed" +fi + +echo "" +echo "=== Test Summary ===" +echo " PASS: $PASS" +echo " FAIL: $FAIL" +echo "" + +if [ "$FAIL" -gt 0 ]; then + echo "❌ $FAIL test(s) failed" + exit 1 +else + echo "✅ All tests passed" + exit 0 +fi