#!/usr/bin/env bash # tg-task — wrap any long-running command so Telegram gets: # 1. an immediate "starting" heartbeat before the command runs # 2. an interim "still running (Ns elapsed)" heartbeat every HEARTBEAT_SECS # 3. a final "done in Ns" + the command's stdout (truncated) # # Usage: # tg-task "label" -- # tg-task --target 3 "rendering avatar" -- python3 /tmp/avatar.py # tg-task "apt install python3" -- apt-get install -y python3 # # Env: # HEARTBEAT_SECS — interim heartbeat interval (default 8s) # TG_STREAM — path to tg-stream (default /host/root/openclaw/tg-stream) # TG_CHAT — chat to notify (default tg-stream's default — StarCros) # # Why this exists: the rule "send a heartbeat every ~5 seconds during long # work" was previously a behavioral memory that I had to remember every # session. That burns tokens and I can forget. Wrapping it as a script makes # the behavior deterministic and free. set -u TG_STREAM="${TG_STREAM:-/host/root/openclaw/tg-stream}" HEARTBEAT_SECS="${HEARTBEAT_SECS:-8}" TARGET=2 # Parse options before -- LABEL="" while [[ $# -gt 0 ]]; do case "$1" in --target) TARGET="$2"; shift 2;; --) shift; break;; *) if [[ -z "$LABEL" ]]; then LABEL="$1"; shift else break fi ;; esac done if [[ -z "$LABEL" ]]; then echo "usage: tg-task [--target SECS] \"label\" -- " >&2 exit 2 fi if [[ $# -eq 0 ]]; then echo "tg-task: missing command after --" >&2 exit 2 fi START_TS=$(date +%s) # Heartbeat 1: starting "$TG_STREAM" --target "$TARGET" "🔧 starting · $LABEL" >/dev/null 2>&1 || true # Run command in background, capture output TMPOUT=$(mktemp /tmp/tg-task-out.XXXXXX) TMPERR=$(mktemp /tmp/tg-task-err.XXXXXX) "$@" >"$TMPOUT" 2>"$TMPERR" & PID=$! # Heartbeat loop LAST_HEARTBEAT=$START_TS while kill -0 "$PID" 2>/dev/null; do sleep 1 NOW=$(date +%s) if (( NOW - LAST_HEARTBEAT >= HEARTBEAT_SECS )); then ELAPSED=$((NOW - START_TS)) "$TG_STREAM" --no-stream "⏳ still on it · $LABEL · ${ELAPSED}s elapsed" >/dev/null 2>&1 || true LAST_HEARTBEAT=$NOW fi done wait "$PID" RC=$? END_TS=$(date +%s) DURATION=$((END_TS - START_TS)) # Final report OUT_SNIPPET=$(tail -c 1500 "$TMPOUT") ERR_SNIPPET=$(tail -c 500 "$TMPERR") STATUS=$([[ $RC -eq 0 ]] && echo "✅ done" || echo "❌ failed (rc=$RC)") REPORT="$STATUS · $LABEL · ${DURATION}s" if [[ -n "$OUT_SNIPPET" ]]; then REPORT="$REPORT stdout: $OUT_SNIPPET" fi if [[ -n "$ERR_SNIPPET" && $RC -ne 0 ]]; then REPORT="$REPORT stderr: $ERR_SNIPPET" fi "$TG_STREAM" --target "$TARGET" "$REPORT" >/dev/null 2>&1 || true # Also dump the full output to stdout/stderr so the caller can pipe/inspect cat "$TMPOUT" [[ -s "$TMPERR" ]] && cat "$TMPERR" >&2 rm -f "$TMPOUT" "$TMPERR" exit $RC