diff options
Diffstat (limited to '.ai/scripts/self-inject.sh')
| -rwxr-xr-x | .ai/scripts/self-inject.sh | 68 |
1 files changed, 68 insertions, 0 deletions
diff --git a/.ai/scripts/self-inject.sh b/.ai/scripts/self-inject.sh new file mode 100755 index 0000000..e7340c1 --- /dev/null +++ b/.ai/scripts/self-inject.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# self-inject.sh — type text into the tmux pane running this agent session. +# +# The building block for AUTO-FLUSH: an agent checkpoints its session-context, +# then has tmux type "/clear" and a resume prompt at its own idle prompt, so a +# session flushes with no human at the keyboard. +# +# Usage: +# self-inject.sh -t %PANE <delay> <text> [<delay2> <text2> ...] +# self-inject.sh <delay> <text> [...] # derive pane from ancestry +# self-inject.sh [-t %PANE] # no pairs: report the pane +# +# Each pair: sleep <delay> seconds, then type <text> literally and press Enter. +# +# TWO HARD-WON GOTCHAS (2026-07-02, archsetup session): +# 1. A detached child (setsid/nohup/&) of an agent tool call DIES when the +# tool call ends — the harness cleans up the process group. The arm step +# must run under the tmux SERVER instead: +# tmux run-shell -b "self-inject.sh -t %1 25 '/clear' 15 'go — resume...'" +# 2. Under tmux run-shell the process is a child of the tmux server, so +# ancestry-based pane detection CANNOT work there. Derive the pane FIRST, +# synchronously from the agent's own shell (no -t), then pass it +# explicitly with -t when arming. +# +# Collision hazard: if the user happens to be typing when the send fires, the +# injected text merges into their input line (a real /clear became "/clearto" +# mid-word). Auto-flush is for sessions running unattended; warn the user to +# keep hands off for the armed window if they're present. + +PANE="" +if [ "$1" = "-t" ]; then + PANE=$2; shift 2 +fi + +ppid_of() { + # /proc/<pid>/stat: pid (comm) state ppid ... — comm may contain spaces, + # so take the 2nd field after the LAST ')'. + stat=$(cat "/proc/$1/stat" 2>/dev/null) || return 1 + # shellcheck disable=SC2086 # word-splitting the stat tail is the point + set -- ${stat##*) } + echo "$2" +} + +find_pane() { + anc=" " + pid=$$ + while [ -n "$pid" ] && [ "$pid" -gt 1 ] 2>/dev/null; do + anc="$anc$pid " + pid=$(ppid_of "$pid") || break + done + tmux list-panes -a -F "#{pane_pid} #{pane_id}" 2>/dev/null | \ + while read -r ppid pane; do + case "$anc" in *" $ppid "*) echo "$pane"; break;; esac + done +} + +[ -n "$PANE" ] || PANE=$(find_pane) +[ -n "$PANE" ] || { echo "self-inject: no owning pane found (pass -t %PANE)" >&2; exit 1; } + +# With no delay/text pairs, just report the pane (the derive-first step). +[ $# -ge 2 ] || { echo "$PANE"; exit 0; } + +while [ $# -ge 2 ]; do + sleep "$1" + tmux send-keys -t "$PANE" -l "$2" + tmux send-keys -t "$PANE" Enter + shift 2 +done |
