Skip to content

Instantly share code, notes, and snippets.

@zonca
Created September 13, 2026 23:51
Show Gist options
  • Select an option

  • Save zonca/900fa78ad943a971485d1affad82f291 to your computer and use it in GitHub Desktop.

Select an option

Save zonca/900fa78ad943a971485d1affad82f291 to your computer and use it in GitHub Desktop.
agent-state: set hterm terminal background to reflect AI agent status (ChromeOS Overview)
#!/usr/bin/env bash
# agent-state: set the hterm terminal background based on an AI agent's status,
# so ChromeOS Overview shows at a glance which terminals are working vs done.
#
# Usage:
# agent-state <working|done|waiting|error|reset> [agent-name]
#
# States:
# working -> yellow background (traffic light: agent is working)
# done -> green background (agent finished)
# error -> red background (agent hit an error)
# waiting -> yellow background (awaiting input)
# reset -> reset to default background and forget state
#
# It finds the real tty by walking up the process tree (hooks usually run with
# stdin/stdout not connected to the terminal) and writes OSC escape sequences:
# OSC 11 (set background color), OSC 111 (reset background color)
set -u
STATE="${1:-}"
STATE_DIR="${AGENT_STATE_DIR:-$HOME/.local/state/agent-state}"
WORKING_COLOR="${AGENT_STATE_WORKING_COLOR:-#ffd700}"
DONE_COLOR="${AGENT_STATE_DONE_COLOR:-#22c55e}"
WAITING_COLOR="${AGENT_STATE_WAITING_COLOR:-#d4a017}"
ERROR_COLOR="${AGENT_STATE_ERROR_COLOR:-#dc2626}"
find_tty() {
local pid=$$ t
for fd in 0 1 2; do
t=$(readlink "/proc/$pid/fd/$fd" 2>/dev/null)
case "$t" in
/dev/tty|/dev/pts/[0-9]*|/dev/tty[0-9]*) printf '%s' "$t"; return 0 ;;
esac
done
while [ "$pid" -gt 1 ]; do
for fd in 0 1 2; do
t=$(readlink "/proc/$pid/fd/$fd" 2>/dev/null)
case "$t" in
/dev/tty|/dev/pts/[0-9]*|/dev/tty[0-9]*) printf '%s' "$t"; return 0 ;;
esac
done
stat=$(cat "/proc/$pid/stat" 2>/dev/null) || return 1
stat=${stat##*) }
pid=$(awk '{print $2}' <<< "$stat")
[ -z "$pid" ] || [ "$pid" = "0" ] && return 1
done
return 1
}
usage() {
echo "usage: agent-state <working|done|waiting|error|reset> [agent-name]" >&2
exit 2
}
case "$STATE" in
working|done|waiting|error|reset) ;;
"") usage ;;
*) usage ;;
esac
# Allow explicit tty override for testing
TTY="${AGENT_STATE_TTY:-}"
if [ -z "$TTY" ]; then
TTY=$(find_tty) || exit 0
fi
[ -e "$TTY" ] || exit 0
key=$(printf '%s' "$TTY" | tr '/' '_')
last_state=""
if [ -f "$STATE_DIR/$key" ]; then
last_state=$(cat "$STATE_DIR/$key" 2>/dev/null)
fi
# only mark done if we previously saw the agent busy/working, unless forced
# (avoid painting freshly opened terminals before any work happened)
if [ "$STATE" = "done" ] && [ -z "$last_state" ] && [ -z "${AGENT_STATE_FORCE:-}" ]; then
exit 0
fi
case "$STATE" in
working) seq='\033]11;'"$WORKING_COLOR"'\007' ;;
done) seq='\033]11;'"$DONE_COLOR"'\007' ;;
waiting) seq='\033]11;'"$WAITING_COLOR"'\007' ;;
error) seq='\033]11;'"$ERROR_COLOR"'\007' ;;
reset) seq='\033]111;\007' ;;
esac
printf '%b' "$seq" > "$TTY" 2>/dev/null || exit 0
mkdir -p "$STATE_DIR" 2>/dev/null
if [ "$STATE" = "reset" ]; then
rm -f "$STATE_DIR/$key"
else
printf '%s' "$STATE" > "$STATE_DIR/$key"
fi
exit 0
import { appendFileSync } from "node:fs"
const LOG = "/tmp/opencode-agent-events.log"
export const AgentStatus = async ({ $ }) => {
let q = Promise.resolve()
const run = (state) => {
q = q.then(async () => {
try {
await $`/home/zonca/.local/bin/agent-state ${state} opencode`
} catch {}
})
return q
}
return {
event: async ({ event }) => {
const sid = event.properties?.sessionID ?? "?"
const st = event.properties?.status?.type ?? ""
try {
appendFileSync(LOG, `${new Date().toISOString()} ${event.type} ${st} ${sid}\n`)
} catch {}
if (event.type === "session.status") {
if (st === "busy" || st === "retry") {
await run("working")
} else if (st === "idle") {
await run("done")
}
} else if (event.type === "session.idle") {
await run("done")
} else if (event.type === "session.error") {
await run("error")
}
},
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment