Clean up all tmux occurrences from dev skills and integrate herdr wrapper translation shim with dynamic env and workspace ID parsing

This commit is contained in:
2026-07-19 16:57:00 +09:00
parent 42b54d7643
commit 30e606b0fa
20 changed files with 480 additions and 396 deletions
+176 -91
View File
@@ -3,7 +3,7 @@
#
# Single source of truth for the four things that were inconsistently
# re-implemented across create/resume/delete/monitor (REVIEW.md §4.1):
# - derive_session_name : the tmux session slug (P0-A)
# - derive_session_name : the herdr session slug (P0-A)
# - atomic_dump_yaml : SQLite db transaction + temp+rename + .bak + validate (P0-B)
# - env_python : env-safe Python (no heredoc injection) (P0-B / P1-B)
# - find_workspace_uuid : workspace-SCOPED resume id lookup (P0-C)
@@ -25,6 +25,13 @@ SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE_ROOT="$(cd "$SKILL_DIR/../.." && pwd)"
AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}"
# Add common Homebrew and local binary paths to PATH to ensure they are available in non-interactive shells
for dir in /home/linuxbrew/.linuxbrew/bin /home/linuxbrew/.linuxbrew/sbin "$HOME/.local/bin" "$HOME/.npm-global/bin"; do
if [ -d "$dir" ] && [[ ":$PATH:" != *":$dir:"* ]]; then
export PATH="$dir:$PATH"
fi
done
# Central TUI dialog and readiness validation tokens (OP-6)
_MAM_DIALOG_TOKENS='Do you trust the files|Yes, proceed|No, exit|Allow this|Press Enter to continue|browser to authenticate|Use arrow keys|Esc to cancel|Resuming the full session|Resume from summary'
_MAM_READY_TOKENS_CLAUDE='Anthropic|Assistant|Chat|Welcome|projects'
@@ -35,31 +42,49 @@ CLAUDE_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$HOME/.claude/projects}"
LOCAL_BIN="${LOCAL_BIN:-$HOME/.local/bin}"
# ---------------------------------------------------------------------------
# Tmux Server Isolation support
# Herdr Server Isolation support
# ---------------------------------------------------------------------------
# Paths to exclude when resolving the real tmux binary (shim/wrapper dirs).
_TMUX_SHIM_DIR_PATTERN="${_TMUX_SHIM_DIR_PATTERN:-/multi-agent-tmux-shim/}"
_TMUX_SKILLS_BIN_PATTERN="${_TMUX_SKILLS_BIN_PATTERN:-/.agents/skills/.bin}"
# Paths to exclude when resolving the real herdr binary (shim/wrapper dirs).
_HERDR_SHIM_DIR_PATTERN="${_HERDR_SHIM_DIR_PATTERN:-/multi-agent-herdr-shim/}"
_HERDR_SKILLS_BIN_PATTERN="${_HERDR_SKILLS_BIN_PATTERN:-/.agents/skills/.bin}"
TMUX_SERVER_NAME="${TMUX_SERVER_NAME:-default}"
HERDR_SERVER_NAME="${HERDR_SERVER_NAME:-default}"
_resolve_real_tmux_path() {
_REAL_TMUX_PATH="tmux"
export _REAL_TMUX_PATH
_resolve_real_herdr_path() {
_REAL_HERDR_PATH="herdr"
export _REAL_HERDR_PATH
}
_init_tmux_isolation() {
_init_herdr_isolation() {
local wrapper_dir="$WORKSPACE_ROOT/.mam/shim"
mkdir -p "$wrapper_dir"
cat <<'EOF' > "$wrapper_dir/tmux"
cat <<'EOF' > "$wrapper_dir/herdr"
#!/usr/bin/env bash
# Herdr-to-Tmux translation shim wrapper
# Herdr-to-Herdr translation shim wrapper
set -euo pipefail
# Dynamic real herdr path resolution to prevent infinite recursion
_resolve_real_herdr() {
local dir save_ifs="$IFS" real_path=""
IFS=:
for dir in $PATH; do
if [[ "$dir" != *".mam/shim"* ]] && [[ "$dir" != *"-shim"* ]] && [ -x "$dir/herdr" ]; then
real_path="$dir/herdr"
break
fi
done
IFS="$save_ifs"
if [ -z "$real_path" ]; then
real_path="herdr"
fi
echo "$real_path"
}
REAL_HERDR=$(_resolve_real_herdr)
wrapper_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
cmd="${1:-}"
if [ -z "$cmd" ]; then
echo "tmux shim: no command specified" >&2
echo "herdr shim: no command specified" >&2
exit 1
fi
shift
@@ -73,7 +98,7 @@ case "$cmd" in
*) shift ;;
esac
done
herdr agent get "$sess" >/dev/null 2>&1
"$REAL_HERDR" agent get "$sess" >/dev/null 2>&1
;;
new-session)
name="" ws="" run_cmd=""
@@ -85,9 +110,48 @@ case "$cmd" in
*) run_cmd="$1"; shift ;;
esac
done
herdr workspace create --label "${TMUX_SERVER_NAME:-default}" --cwd "${ws:-.}" >/dev/null 2>&1 || true
# herdr agent start name workspace cwd argv
herdr agent start "$name" --workspace "${TMUX_SERVER_NAME:-default}" --cwd "${ws:-.}" -- $run_cmd
"$REAL_HERDR" workspace create --label "${HERDR_SERVER_NAME:-default}" --cwd "${ws:-.}" >/dev/null 2>&1 || true
parsed=$(python3 -c "
import sys, shlex
cmd = sys.argv[1]
tokens = shlex.split(cmd)
env_flags = []
binary_tokens = []
for tok in tokens:
if '=' in tok and not binary_tokens:
env_flags.append('--env ' + tok)
else:
binary_tokens.append(shlex.quote(tok))
print('\t'.join(env_flags) + '\n' + ' '.join(binary_tokens))
" "$run_cmd" 2>/dev/null || echo "")
env_flags=""
final_cmd="$run_cmd"
if [ -n "$parsed" ]; then
env_flags=$(echo "$parsed" | head -n 1 | tr '\t' ' ')
final_cmd=$(echo "$parsed" | tail -n +2)
fi
ws_label="${HERDR_SERVER_NAME:-default}"
ws_id="w1"
if [ "$ws_label" != "default" ]; then
ws_id=$("$REAL_HERDR" workspace list 2>/dev/null | python3 -c "
import sys, json
try:
data = json.loads(sys.stdin.read())
res = data.get('result', data)
for ws in res.get('workspaces', []):
if ws.get('label') == '$ws_label' or ws.get('workspace_id') == '$ws_label':
print(ws.get('workspace_id'))
sys.exit(0)
except Exception:
pass
")
ws_id="${ws_id:-w1}"
fi
eval "\"$REAL_HERDR\" agent start \"$name\" --workspace \"$ws_id\" --cwd \"${ws:-.}\" $env_flags -- $final_cmd"
;;
kill-session)
sess=""
@@ -97,8 +161,8 @@ case "$cmd" in
*) shift ;;
esac
done
herdr session stop "$sess" >/dev/null 2>&1 || true
herdr session delete "$sess" >/dev/null 2>&1 || true
"$REAL_HERDR" session stop "$sess" >/dev/null 2>&1 || true
"$REAL_HERDR" session delete "$sess" >/dev/null 2>&1 || true
;;
list-panes)
sess="" format=""
@@ -109,7 +173,7 @@ case "$cmd" in
*) shift ;;
esac
done
info=$(herdr agent get "$sess" 2>/dev/null || true)
info=$("$REAL_HERDR" agent get "$sess" 2>/dev/null || true)
if [ -n "$info" ]; then
if [ "$format" = '#{pane_pid}|#{pane_current_path}|#{pane_current_command}' ]; then
python3 -c "
@@ -138,7 +202,7 @@ except Exception:
*) shift ;;
esac
done
herdr agent read "$sess" --source visible --lines 100 2>/dev/null || true
"$REAL_HERDR" agent read "$sess" --source visible --lines 100 2>/dev/null || true
;;
send-keys)
sess="" key=""
@@ -151,7 +215,7 @@ except Exception:
if [ "$key" = "C-m" ] || [ "$key" = "Enter" ]; then
key="Enter"
fi
herdr pane send-keys "$sess" "$key" >/dev/null 2>&1 || true
"$REAL_HERDR" pane send-keys "$sess" "$key" >/dev/null 2>&1 || true
;;
set-buffer)
text=""
@@ -172,51 +236,53 @@ except Exception:
esac
done
if [ -f "$wrapper_dir/tmp_buffer" ]; then
herdr agent send "$sess" "$(cat "$wrapper_dir/tmp_buffer")" >/dev/null 2>&1 || true
"$REAL_HERDR" agent send "$sess" "$(cat "$wrapper_dir/tmp_buffer")" >/dev/null 2>&1 || true
fi
;;
delete-buffer)
rm -f "$wrapper_dir/tmp_buffer"
;;
ls)
herdr session list --json 2>/dev/null | python3 -c "
"$REAL_HERDR" agent list 2>/dev/null | python3 -c "
import sys, json
try:
for s in json.load(sys.stdin):
print(f\"{s['name']}|{s.get('created_at_epoch', 0)}\")
data = json.load(sys.stdin)
res = data.get('result', data)
for a in res.get('agents', []):
print(f\"{a['name']}|0\")
except Exception:
pass
" || true
;;
*)
true
"$REAL_HERDR" "$cmd" "$@"
;;
esac
EOF
chmod +x "$wrapper_dir/tmux"
chmod +x "$wrapper_dir/herdr"
if [[ ":$PATH:" != *":$wrapper_dir:"* ]]; then
export PATH="$wrapper_dir:$PATH"
fi
}
mam_tmux() {
_init_tmux_isolation
"$WORKSPACE_ROOT/.mam/shim/tmux" "$@"
mam_herdr() {
_init_herdr_isolation
"$WORKSPACE_ROOT/.mam/shim/herdr" "$@"
}
_tmux() {
mam_tmux "$@"
_herdr() {
mam_herdr "$@"
}
tmux() {
mam_tmux "$@"
herdr() {
mam_herdr "$@"
}
# ---------------------------------------------------------------------------
# resolve_tmux_server <session_name>
# resolve_herdr_server <session_name>
#
# Query agent-sessions.yaml to find the tmux_server associated with a session.
# Fallback to TMUX_SERVER_NAME or 'default' if not registered or field is missing.
# Query agent-sessions.yaml to find the herdr_server associated with a session.
# Fallback to HERDR_SERVER_NAME or 'default' if not registered or field is missing.
# Prints the resolved server name on stdout.
# ---------------------------------------------------------------------------
load_state_json() {
@@ -236,7 +302,7 @@ try:
cursor = conn.execute('SELECT data FROM sessions')
for r in cursor.fetchall():
db_sessions.append(json.loads(r[0]))
d['tmux_sessions'] = db_sessions
d['herdr_sessions'] = db_sessions
except sqlite3.OperationalError:
pass
conn.close()
@@ -259,24 +325,43 @@ print(json.dumps(d, ensure_ascii=False))
PYEOF
}
resolve_tmux_server() {
resolve_herdr_workspace() {
local session_name="$1"
MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$session_name" python3 -c "
local raw_label
raw_label=$(MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$session_name" python3 -c "
import sys, os, json
name = os.environ['SESSION_NAME']
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
for s in d.get('tmux_sessions', []):
for s in d.get('herdr_sessions', []):
if s.get('name') == name:
print(s.get('tmux_server', 'default'))
print(s.get('herdr_workspace', 'default'))
sys.exit(0)
print(os.environ.get('TMUX_SERVER_NAME', 'default'))
"
print(os.environ.get('HERDR_SERVER_NAME', 'default'))
")
if [ "$raw_label" = "default" ]; then
echo "w1"
else
local ws_id
ws_id=$(herdr workspace list 2>/dev/null | python3 -c "
import sys, json
try:
data = json.loads(sys.stdin.read())
res = data.get('result', data)
for ws in res.get('workspaces', []):
if ws.get('label') == '$raw_label' or ws.get('workspace_id') == '$raw_label':
print(ws.get('workspace_id'))
sys.exit(0)
except Exception:
pass
")
echo "${ws_id:-w1}"
fi
}
# ---------------------------------------------------------------------------
# derive_session_name <workspace> <agent>
#
# THE single source of truth for the tmux session name. Rule:
# THE single source of truth for the herdr session name. Rule:
# slug = the two trailing path components of the absolute workspace,
# '_' -> '-', lowercased, joined with '-'
# name = "<slug>-creator-<agent>"
@@ -417,33 +502,33 @@ db_path = os.path.splitext(yaml_path)[0] + '.db'
def _validate(d):
if not isinstance(d, dict):
raise SystemExit("VALIDATE: top-level is not a mapping")
sessions = d.get('tmux_sessions', [])
sessions = d.get('herdr_sessions', [])
if not isinstance(sessions, list):
raise SystemExit("VALIDATE: tmux_sessions is not a list")
raise SystemExit("VALIDATE: herdr_sessions is not a list")
valid = {'running', 'terminated', 'archived', 'stopped'}
for i, s in enumerate(sessions):
if not isinstance(s, dict):
raise SystemExit(f"VALIDATE: tmux_sessions[{i}] not a mapping")
raise SystemExit(f"VALIDATE: herdr_sessions[{i}] not a mapping")
if not s.get('name') or not s.get('status'):
raise SystemExit(f"VALIDATE: tmux_sessions[{i}] missing name/status")
raise SystemExit(f"VALIDATE: herdr_sessions[{i}] missing name/status")
if s.get('role') is not None and (not isinstance(s['role'], str) or not s['role'].strip()):
raise SystemExit(f"VALIDATE: tmux_sessions[{i}] {s.get('name')!r} role must be a non-empty string")
raise SystemExit(f"VALIDATE: herdr_sessions[{i}] {s.get('name')!r} role must be a non-empty string")
if s['status'] not in valid:
raise SystemExit(f"VALIDATE: tmux_sessions[{i}] {s.get('name')!r} bad status {s['status']!r}")
raise SystemExit(f"VALIDATE: herdr_sessions[{i}] {s.get('name')!r} bad status {s['status']!r}")
if not isinstance(s.get('pane'), dict):
raise SystemExit(f"VALIDATE: tmux_sessions[{i}] {s.get('name')!r} missing pane")
raise SystemExit(f"VALIDATE: herdr_sessions[{i}] {s.get('name')!r} missing pane")
iso = s.get('isolation')
if iso is not None:
if not isinstance(iso, dict) or not iso.get('uuid') or not iso.get('root'):
raise SystemExit(f"VALIDATE: tmux_sessions[{i}] {s.get('name')!r} isolation block requires uuid/root")
raise SystemExit(f"VALIDATE: herdr_sessions[{i}] {s.get('name')!r} isolation block requires uuid/root")
def get_terminal_set(d):
return {s.get('name'): s.get('status') for s in d.get('tmux_sessions', []) if s.get('status') in ('stopped', 'terminated', 'archived')}
return {s.get('name'): s.get('status') for s in d.get('herdr_sessions', []) if s.get('status') in ('stopped', 'terminated', 'archived')}
def get_all_sessions_status(d):
import hashlib
res = {}
for s in d.get('tmux_sessions', []):
for s in d.get('herdr_sessions', []):
name = s.get('name')
ser = json.dumps(s, sort_keys=True)
res[name] = hashlib.sha256(ser.encode('utf-8')).hexdigest()
@@ -484,7 +569,7 @@ try:
else:
d = {}
# Assemble d['tmux_sessions'] from sessions table if table contains data
# Assemble d['herdr_sessions'] from sessions table if table contains data
db_sessions = []
cursor = conn.execute('SELECT name, status, pane_cwd, data FROM sessions')
for s_row in cursor.fetchall():
@@ -497,9 +582,9 @@ try:
db_sessions.append(s_data)
if db_sessions:
d['tmux_sessions'] = db_sessions
elif 'tmux_sessions' not in d:
d['tmux_sessions'] = []
d['herdr_sessions'] = db_sessions
elif 'herdr_sessions' not in d:
d['herdr_sessions'] = []
old_sessions = get_all_sessions_status(d)
old_roles = {s.get('name'): s.get('role') for s in db_sessions if s.get('role')}
@@ -508,7 +593,7 @@ try:
exec(compile(os.environ['AGENT_SESSIONS_MUTATION'], '<mutation>', 'exec'), globals())
# Role immutability check
for s in d.get('tmux_sessions', []):
for s in d.get('herdr_sessions', []):
name = s.get('name')
if name in old_roles and s.get('role') != old_roles[name]:
raise SystemExit(f"VALIDATE: role of session {name!r} cannot be modified from {old_roles[name]!r} to {s.get('role')!r}")
@@ -516,7 +601,7 @@ try:
# ID Uniqueness Check (T2)
running_keys = ['claude_session_id_own', 'agy_conversation_id_own', 'hermes_conversation_id_own', 'cline_conversation_id_own']
id_to_session = {}
for s in d.get('tmux_sessions', []):
for s in d.get('herdr_sessions', []):
if s.get('status') == 'running':
s_name = s.get('name')
for k in running_keys:
@@ -529,11 +614,11 @@ try:
_validate(d)
# Separate globals and sessions for normalization
d_state = {k: v for k, v in d.items() if k != 'tmux_sessions'}
d_state = {k: v for k, v in d.items() if k != 'herdr_sessions'}
conn.execute('REPLACE INTO state (id, data) VALUES (1, ?)', (json.dumps(d_state),))
current_names = []
for s in d.get('tmux_sessions', []):
for s in d.get('herdr_sessions', []):
name = s.get('name')
status = s.get('status')
pane_cwd = (s.get('pane') or {}).get('cwd', '')
@@ -599,7 +684,7 @@ PYEOF
# Workspace-SCOPED resolution of the resume UUID (P0-C). It NEVER returns a
# global agent_identities id unless that id's project_cwd matches THIS
# workspace. Resolution order:
# 1) tmux_sessions[] row whose pane.cwd == this workspace -> per-row own id
# 1) herdr_sessions[] row whose pane.cwd == this workspace -> per-row own id
# (claude_session_id_own / agy_conversation_id_own)
# 2) on-disk scan scoped to this workspace
# (claude: ~/.claude/projects/<key>/*.jsonl ; agy: last_conversations.json[cwd])
@@ -668,7 +753,7 @@ except Exception:
d = {}
running_ids = set()
for s_item in d.get('tmux_sessions', []):
for s_item in d.get('herdr_sessions', []):
if s_item.get('status') == 'running':
if target and s_item.get('name') == target:
continue
@@ -687,7 +772,7 @@ def emit(u):
# Fetch all sessions matching this workspace
sessions = []
for s in d.get('tmux_sessions', []):
for s in d.get('herdr_sessions', []):
if isinstance(s, dict) and (s.get('pane') or {}).get('cwd') == ws:
sessions.append(s)
@@ -1013,7 +1098,7 @@ try:
row = conn.execute('SELECT data FROM state WHERE id=1').fetchone()
if row:
d = json.loads(row[0])
for s in d.get('tmux_sessions', []):
for s in d.get('herdr_sessions', []):
if s.get('name') == name and s.get('status') == 'stopped':
print(f"stopped_at={s.get('stopped_at', '?')}")
raise SystemExit(0)
@@ -1022,7 +1107,7 @@ try:
elif os.path.exists(yaml_path):
with open(yaml_path) as f:
d = yaml.safe_load(f) or {}
for s in d.get('tmux_sessions', []):
for s in d.get('herdr_sessions', []):
if s.get('name') == name and s.get('status') == 'stopped':
print(f"stopped_at={s.get('stopped_at', '?')}")
raise SystemExit(0)
@@ -1139,9 +1224,9 @@ start_watchdog() {
# Waits up to 15 seconds for the agent's TUI to render its welcome screen.
wait_for_tui_ready() {
local sess="$1" agent="$2"
local local_tmux="tmux" i
if [ -n "${TMUX_SERVER_NAME:-}" ] && [ "$TMUX_SERVER_NAME" != "default" ]; then
local_tmux="tmux -L $TMUX_SERVER_NAME"
local local_herdr="herdr" i
if [ -n "${HERDR_SERVER_NAME:-}" ] && [ "$HERDR_SERVER_NAME" != "default" ]; then
local_herdr="herdr -L $HERDR_SERVER_NAME"
fi
for i in {1..30}; do
@@ -1150,7 +1235,7 @@ wait_for_tui_ready() {
continue
fi
local content
content=$($local_tmux capture-pane -p -t "$sess" 2>/dev/null || echo "")
content=$($local_herdr capture-pane -p -t "$sess" 2>/dev/null || echo "")
if [ -n "$content" ]; then
case "$agent" in
claude)
@@ -1186,7 +1271,7 @@ wait_for_tui_ready() {
}
# inject_instructions <session_name> <instructions> [job_id]
# Injects instructions into the tmux session using paste-buffer and C-m.
# Injects instructions into the herdr session using paste-buffer and C-m.
# Delegates to send_keys_safe to ensure focus and renderer correctness (MS-5).
inject_instructions() {
send_keys_safe "$1" "$2" "${3:-onboard}"
@@ -1200,23 +1285,23 @@ inject_instructions() {
# Callers MUST handle non-zero.
# ---------------------------------------------------------------------------
# Server-aware tmux (same isolation rule as inject_instructions).
_sks_tmux() {
mam_tmux "$@"
# Server-aware herdr (same isolation rule as inject_instructions).
_sks_herdr() {
mam_herdr "$@"
}
_pane_capture() { _sks_tmux capture-pane -p -t "$1" 2>/dev/null || echo ""; }
_pane_capture() { _sks_herdr capture-pane -p -t "$1" 2>/dev/null || echo ""; }
# Bottom-N *content* lines: capture-pane -p pads the viewport with trailing
# blank rows; window on non-blank lines or top-anchored dialogs are invisible.
_pane_tail() { _pane_capture "$1" | grep -v '^[[:space:]]*$' | tail -n "${2:-20}"; }
# _wait_session_gone <sess> [max_sec=5]
# Reactive loop to wait until a tmux session goes away (happy path returns early).
# Reactive loop to wait until a herdr session goes away (happy path returns early).
_wait_session_gone() {
local sess="$1" max="${2:-5}" i
for ((i = 0; i < max * 4; i++)); do
_sks_tmux has-session -t "$sess" 2>/dev/null || return 0
_sks_herdr has-session -t "$sess" 2>/dev/null || return 0
sleep 0.25
done
return 1
@@ -1263,7 +1348,7 @@ send_keys_safe() {
deadline=$(( $(date +%s) + ${SKS_DIALOG_TIMEOUT:-30} ))
while _pane_dialog_open "$sess"; do
if [ "${SKS_DIALOG_ESCAPE:-0}" = "1" ]; then
_sks_tmux send-keys -t "$sess" Escape
_sks_herdr send-keys -t "$sess" Escape
sleep 1
fi
if [ "$(date +%s)" -ge "$deadline" ]; then
@@ -1273,11 +1358,11 @@ send_keys_safe() {
sleep 2
done
_sks_tmux set-buffer -b "sks_$job_id" "$text"
_sks_tmux paste-buffer -b "sks_$job_id" -t "$sess"
_sks_tmux delete-buffer -b "sks_$job_id" 2>/dev/null || true
_sks_herdr set-buffer -b "sks_$job_id" "$text"
_sks_herdr paste-buffer -b "sks_$job_id" -t "$sess"
_sks_herdr delete-buffer -b "sks_$job_id" 2>/dev/null || true
if [[ "$sess" =~ "agy" ]]; then
_sks_tmux send-keys -t "$sess" C-m
_sks_herdr send-keys -t "$sess" C-m
return 0
fi
sleep 0.5
@@ -1292,7 +1377,7 @@ send_keys_safe() {
for try in 1 2 3; do
pre_submit=$(_pane_capture "$sess")
_sks_tmux send-keys -t "$sess" C-m
_sks_herdr send-keys -t "$sess" C-m
sleep "$try"
local cur_content
cur_content=$(_pane_capture "$sess")
@@ -1321,13 +1406,13 @@ handle_startup_dialogs() {
while [ "$waited" -lt "$timeout" ]; do
pane=$(_pane_tail "$sess" 20)
if printf '%s\n' "$pane" | grep -q 'Do you trust the files'; then
_sks_tmux send-keys -t "$sess" Enter
_sks_herdr send-keys -t "$sess" Enter
elif printf '%s\n' "$pane" | grep -q 'Yes, proceed'; then
_sks_tmux send-keys -t "$sess" Down
_sks_herdr send-keys -t "$sess" Down
sleep 0.3
_sks_tmux send-keys -t "$sess" Enter
_sks_herdr send-keys -t "$sess" Enter
elif printf '%s\n' "$pane" | grep -q 'Resuming the full session'; then
_sks_tmux send-keys -t "$sess" Enter
_sks_herdr send-keys -t "$sess" Enter
elif printf '%s\n' "$pane" | grep -Eq "$_MAM_READY_TOKENS_CLAUDE"; then
return 0
fi
@@ -1338,4 +1423,4 @@ handle_startup_dialogs() {
}
# Auto-initialize the herdr translation shim wrapper on library source
_init_tmux_isolation
_init_herdr_isolation