Align conftest mock herdr and unit test assertions with Claude's simplified session-based isolation and native herdr command updates

This commit is contained in:
2026-07-20 07:42:05 +09:00
parent cccc30a8ac
commit 6df4b03661
13 changed files with 312 additions and 115 deletions
+159 -66
View File
@@ -22,7 +22,7 @@ if [ -z "${BASH_VERSION:-}" ]; then
return 1 2>/dev/null || exit 1
fi
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE_ROOT="$(cd "$SKILL_DIR/../.." && pwd)"
WORKSPACE_ROOT="${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
@@ -100,6 +100,46 @@ while [ "${1:-}" = "-L" ]; do
shift 2
done
# Herdr's real isolation boundary is `--session <name>` (a whole separate
# server + socket, like tmux `-L`) — NOT `workspace create --label`, which is
# just a named subdivision inside ONE server and provides no actual isolation
# (agent/pane commands are server-global regardless of workspace). When
# HERDR_SERVER_NAME names a non-default session, make sure its headless server
# is actually running, then scope every real herdr call to it via `--session`.
_MAM_SESSION="${HERDR_SERVER_NAME:-default}"
if [ "$_MAM_SESSION" = "default" ]; then
_MAM_SESSION=""
else
_mam_session_running=$("$REAL_HERDR" session list --json 2>/dev/null | MAM_SESS="$_MAM_SESSION" python3 -c "
import sys, json, os
try:
d = json.loads(sys.stdin.read())
name = os.environ.get('MAM_SESS', '')
print('1' if any(s.get('name') == name and s.get('running') for s in d.get('sessions', [])) else '0')
except Exception:
print('0')
" 2>/dev/null || echo "0")
if [ "$_mam_session_running" != "1" ]; then
# Headless bootstrap avoids herdr's "nested herdr is disabled" guard that
# blocks a normal interactive `herdr --session <name>` launch from inside
# an existing herdr pane (which is how MAM's own agents usually run).
nohup "$REAL_HERDR" --session "$_MAM_SESSION" server >/dev/null 2>&1 &
disown 2>/dev/null || true
for _mam_wait_i in $(seq 1 40); do
[ -S "${HOME:-$HOME_DIR}/.config/herdr/sessions/$_MAM_SESSION/herdr.sock" ] && break
sleep 0.25
done
fi
fi
_real_herdr() {
if [ -n "$_MAM_SESSION" ]; then
"$REAL_HERDR" --session "$_MAM_SESSION" "$@"
else
"$REAL_HERDR" "$@"
fi
}
wrapper_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
cmd="${1:-}"
if [ -z "$cmd" ]; then
@@ -125,7 +165,7 @@ case "$cmd" in
*) shift ;;
esac
done
"$REAL_HERDR" agent get "$sess" >/dev/null 2>&1
_real_herdr agent get "$sess" >/dev/null 2>&1
;;
new-session)
name="" ws="" run_cmd=""
@@ -154,8 +194,7 @@ case "$cmd" in
if [ -z "$run_cmd" ]; then
run_cmd="${SHELL:-/bin/bash}"
fi
"$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]
@@ -177,26 +216,22 @@ print('\t'.join(env_flags) + '\n' + ' '.join(binary_tokens))
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 | WS_LABEL="$ws_label" python3 -c "
import sys, os, json
# Isolation now comes from `_MAM_SESSION` (a real, separate herdr
# session/server) rather than a workspace label match — just create a
# fresh workspace inside whatever session is active (default or
# isolated) and place the agent there. No cross-session label lookup
# needed since `--session` already scopes everything.
ws_id=$(_real_herdr workspace create --cwd "${ws:-.}" --no-focus 2>/dev/null | python3 -c "
import sys, json
try:
ws_label = os.environ.get('WS_LABEL', '')
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)
d = json.loads(sys.stdin.read())
print(d.get('result', {}).get('workspace', {}).get('workspace_id', ''))
except Exception:
pass
")
ws_id="${ws_id:-w1}"
fi
ws_id="${ws_id:-w1}"
eval "\"$REAL_HERDR\" agent start \"$name\" --workspace \"$ws_id\" --cwd \"${ws:-.}\" $env_flags -- $final_cmd"
eval "_real_herdr agent start \"$name\" --workspace \"$ws_id\" --cwd \"${ws:-.}\" $env_flags -- $final_cmd"
;;
kill-session)
sess=""
@@ -213,8 +248,20 @@ except Exception:
*) shift ;;
esac
done
"$REAL_HERDR" session stop "$sess" >/dev/null 2>&1 || true
"$REAL_HERDR" session delete "$sess" >/dev/null 2>&1 || true
# NOTE: herdr's `session` verb operates on whole server instances (see
# `herdr session list`), not individual agent panes — `session stop/delete
# "$sess"` with a MAM session name always fails (silently, via `|| true`).
# Resolve the real pane_id via `agent get` and close just that pane instead.
pane_id=$(_real_herdr agent get "$sess" 2>/dev/null | python3 -c "
import sys, json
try:
print(json.load(sys.stdin).get('result', {}).get('agent', {}).get('pane_id', ''))
except Exception:
pass
" 2>/dev/null)
if [ -n "$pane_id" ]; then
_real_herdr pane close "$pane_id" >/dev/null 2>&1 || true
fi
;;
list-panes)
sess="" format=""
@@ -239,26 +286,57 @@ except Exception:
*) shift ;;
esac
done
info=$("$REAL_HERDR" agent get "$sess" 2>/dev/null || true)
# NOTE: `herdr agent get <target>` returns {"result": {"agent": {...}}} —
# there is no top-level "pane" key, and no "pid" field at all (only
# "pane_id", herdr's own wN:pN identifier). The real OS pid requires a
# second call to `pane process-info --pane <pane_id>`.
info=$(_real_herdr agent get "$sess" 2>/dev/null || true)
pane_id="" cwd="" cmd=""
if [ -n "$info" ]; then
if [ "$format" = '#{pane_pid}|#{pane_current_path}|#{pane_current_command}' ]; then
python3 -c "
parsed=$(python3 -c "
import sys, json
try:
d = json.loads(sys.stdin.read())
p = d.get('pane', d)
print(f\"{p.get('pid', '')}|{p.get('cwd', '')}|{p.get('command', p.get('argv', ''))}\")
a = json.loads(sys.stdin.read()).get('result', {}).get('agent', {})
except Exception:
a = {}
print(a.get('pane_id', ''))
print(a.get('cwd', ''))
print(a.get('agent', ''))
" <<< "$info")
pane_id=$(sed -n '1p' <<< "$parsed")
cwd=$(sed -n '2p' <<< "$parsed")
cmd=$(sed -n '3p' <<< "$parsed")
fi
pid=""
if [ -n "$pane_id" ]; then
case "$format" in
*pane_pid*)
pid=$(_real_herdr pane process-info --pane "$pane_id" 2>/dev/null | python3 -c "
import sys, json
try:
pi = json.loads(sys.stdin.read()).get('result', {}).get('process_info', {})
fg = pi.get('foreground_processes') or []
print(fg[0]['pid'] if fg else pi.get('shell_pid', ''))
except Exception:
pass
" <<< "$info"
elif [ "$format" = '#{pane_pid}' ]; then
python3 -c "import sys, json; d = json.loads(sys.stdin.read()); print(d.get('pane', d).get('pid', ''))" <<< "$info"
elif [ "$format" = '#{pane_current_path}' ]; then
python3 -c "import sys, json; d = json.loads(sys.stdin.read()); print(d.get('pane', d).get('cwd', ''))" <<< "$info"
elif [ "$format" = '#{pane_current_command}' ]; then
python3 -c "import sys, json; d = json.loads(sys.stdin.read()); print(d.get('pane', d).get('command', d.get('pane', d).get('argv', '')))" <<< "$info"
fi
")
;;
esac
fi
case "$format" in
'#{pane_pid}|#{pane_current_path}|#{pane_current_command}')
printf '%s|%s|%s\n' "$pid" "$cwd" "$cmd"
;;
'#{pane_pid}')
printf '%s\n' "$pid"
;;
'#{pane_current_path}')
printf '%s\n' "$cwd"
;;
'#{pane_current_command}')
printf '%s\n' "$cmd"
;;
esac
;;
capture-pane)
sess=""
@@ -275,7 +353,7 @@ except Exception:
*) shift ;;
esac
done
"$REAL_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=""
@@ -295,7 +373,18 @@ except Exception:
if [ "$key" = "C-m" ] || [ "$key" = "Enter" ]; then
key="Enter"
fi
"$REAL_HERDR" pane send-keys "$sess" "$key" >/dev/null 2>&1 || true
# `pane send-keys` requires a real pane_id ("wN:pN"), not an agent name —
# resolve it via `agent get` first (agent-level commands accept names).
pane_id=$(_real_herdr agent get "$sess" 2>/dev/null | python3 -c "
import sys, json
try:
print(json.load(sys.stdin).get('result', {}).get('agent', {}).get('pane_id', ''))
except Exception:
pass
" 2>/dev/null)
if [ -n "$pane_id" ]; then
_real_herdr pane send-keys "$pane_id" "$key" >/dev/null 2>&1 || true
fi
;;
set-buffer)
text=""
@@ -330,14 +419,14 @@ except Exception:
esac
done
if [ -f "$wrapper_dir/tmp_buffer" ]; then
"$REAL_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)
"$REAL_HERDR" agent list 2>/dev/null | python3 -c "
_real_herdr agent list 2>/dev/null | python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
@@ -353,7 +442,7 @@ except Exception:
" || true
;;
*)
"$REAL_HERDR" "$cmd" "$@"
_real_herdr "$cmd" "$@"
;;
esac
EOF
@@ -424,10 +513,15 @@ print(json.dumps(d, ensure_ascii=False))
PYEOF
}
# Despite the name (kept for caller compatibility — resume/stop/update_yaml_resumed
# all do `HERDR_SERVER_NAME="$(resolve_herdr_workspace "$SESSION_NAME")"`), this
# returns the isolated herdr *session* name to use for this MAM session row, not
# a workspace id. Real isolation is `--session <name>` (see `_MAM_SESSION` in the
# generated wrapper) — a workspace label match provides no actual isolation
# since agent/pane commands are server-global regardless of workspace.
resolve_herdr_workspace() {
local session_name="$1"
local raw_label
raw_label=$(MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$session_name" python3 -c "
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', '{}'))
@@ -436,26 +530,7 @@ for s in d.get('herdr_sessions', []):
print(s.get('herdr_workspace') or s.get('herdr_server') or 'default')
sys.exit(0)
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 | RAW_LABEL="$raw_label" python3 -c "
import sys, os, json
try:
raw_label = os.environ.get('RAW_LABEL', '')
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
"
}
# ---------------------------------------------------------------------------
@@ -489,6 +564,10 @@ derive_session_name() {
fi
slug="$(printf '%s-%s' "$parent" "$work" | tr '[:upper:]' '[:lower:]' | tr '_' '-')"
slug="$(printf '%s' "$slug" | tr -cd 'a-zA-Z0-9-')"
slug="$(printf '%s' "$slug" | sed 's/^-*//')"
if [ -z "$slug" ]; then
slug="ws"
fi
printf '%s-creator-%s' "$slug" "$agent"
}
@@ -1440,8 +1519,18 @@ _pane_dialog_open() {
send_keys_safe() {
local sess="$1" text="$2" job_id="${3:-adhoc}"
local marker pre_submit deadline try
# Verification token: last 24 chars of the last non-empty line (multi-line safe).
marker=$(printf '%s' "$text" | tr -d '\r' | awk 'NF {line=$0} END {print line}' | tail -c 24)
# Verification token: last 24 *characters* (not bytes — `tail -c` can split a
# multi-byte UTF-8 char, e.g. Korean, producing a marker that can never match
# the properly-decoded rendered pane text) of the last non-empty line.
marker=$(printf '%s' "$text" | tr -d '\r' | awk 'NF {line=$0} END {print line}' | python3 -c "import sys; print(sys.stdin.read().rstrip('\n')[-24:], end='')")
# Whitespace-normalized copy for matching: some TUIs (e.g. cline's own
# message rendering) don't just soft-wrap with a bare newline — they add a
# leading-space "hanging indent" on the continuation line too, so removing
# only '\n' still leaves an extra space that breaks an exact literal match.
# Matching with all whitespace collapsed out sidesteps wrap formatting
# entirely, whatever shape it takes.
local marker_norm
marker_norm=$(printf '%s' "$marker" | tr -d '[:space:]')
_pane_quiescent "$sess" || { echo "send_keys_safe: pane never quiesced ($sess)" >&2; return 1; }
@@ -1470,7 +1559,11 @@ send_keys_safe() {
pane_content=$(_pane_capture "$sess")
if printf '%s\n' "$pane_content" | grep -Eq "Pasted text|paste again to expand"; then
was_popup=1
elif ! printf '%s\n' "$pane_content" | grep -Fq "$marker"; then
# Strip ALL whitespace before matching — the rendered pane soft-wraps long
# lines at the terminal width (and some TUIs add indentation on the
# continuation line too), which can split/reformat the marker across two
# visual lines and make a literal grep miss it even though the paste landed.
elif ! printf '%s' "$pane_content" | tr -d '[:space:]' | grep -Fq "$marker_norm"; then
echo "send_keys_safe: paste not visible ($sess)" >&2
return 3
fi
@@ -1485,7 +1578,7 @@ send_keys_safe() {
if printf '%s\n' "$cur_content" | grep -Eq "● |✽ |[A-Za-z]+ing…|[A-Za-z]+ing\.\.\.|esc to interrupt"; then
return 0
fi
if [ "$was_popup" = "0" ] && ! _pane_tail "$sess" 3 | grep -Fq "$marker" && [ "$cur_content" != "$pre_submit" ]; then
if [ "$was_popup" = "0" ] && ! _pane_tail "$sess" 3 | tr -d '[:space:]' | grep -Fq "$marker_norm" && [ "$cur_content" != "$pre_submit" ]; then
return 0
elif [ "$was_popup" = "1" ] && \
! printf '%s\n' "$cur_content" | grep -Eq "Pasted text|paste again to expand" && \