blob: e7340c180657d3fc7cd47f4d9fdb9f4fae68e775 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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
|