Files

104 lines
2.8 KiB
Bash
Executable File

#!/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" -- <command...>
# 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\" -- <command...>" >&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