fix: attach_command from one builder, no kill/start_command, row-less stop, one herdr name rule, registry format 2 (Issue #71) #73

Merged
Godopu merged 15 commits from fix/issue-71 into main 2026-10-26 22:28:51 +00:00
2 changed files with 45 additions and 52 deletions
Showing only changes of commit 4243da507c - Show all commits

No files matched your search

+43 -50
View File
@@ -33,6 +33,11 @@ MAM_VERSION="4.10.1"
export MAM_VERSION export MAM_VERSION
AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}" AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}"
# The one bash form of lib_py.agents.sanitize.sanitize_herdr_agent_name
# (Issue #71). The herdr shim gets this same definition: _init_herdr_isolation
# writes it in with `declare -f`. A long name needs its SHA-1 suffix; with no
# shasum, sha1sum, openssl or python3 to make it, this fails (stderr, nonzero)
# rather than return a name herdr never registered.
_sanitize_herdr_agent_name() { _sanitize_herdr_agent_name() {
local n="$1" local n="$1"
if [ -z "$n" ]; then if [ -z "$n" ]; then
@@ -46,16 +51,21 @@ _sanitize_herdr_agent_name() {
local h="" local h=""
if command -v shasum >/dev/null 2>&1; then if command -v shasum >/dev/null 2>&1; then
h=$(printf '%s' "$s" | shasum 2>/dev/null | awk '{print $1}' | cut -c 1-8) h=$(printf '%s' "$s" | shasum 2>/dev/null | awk '{print $1}' | cut -c 1-8)
elif command -v sha1sum >/dev/null 2>&1; then fi
if [ "${#h}" -ne 8 ] && command -v sha1sum >/dev/null 2>&1; then
h=$(printf '%s' "$s" | sha1sum 2>/dev/null | awk '{print $1}' | cut -c 1-8) h=$(printf '%s' "$s" | sha1sum 2>/dev/null | awk '{print $1}' | cut -c 1-8)
elif command -v openssl >/dev/null 2>&1; then fi
if [ "${#h}" -ne 8 ] && command -v openssl >/dev/null 2>&1; then
h=$(printf '%s' "$s" | openssl sha1 2>/dev/null | awk '{print $NF}' | cut -c 1-8) h=$(printf '%s' "$s" | openssl sha1 2>/dev/null | awk '{print $NF}' | cut -c 1-8)
fi fi
if [ -n "$h" ]; then if [ "${#h}" -ne 8 ] && command -v python3 >/dev/null 2>&1; then
s="${s:0:23}-$h" h=$(python3 -c 'import hashlib,sys; print(hashlib.sha1(sys.argv[1].encode("utf-8")).hexdigest()[:8])' "$s" 2>/dev/null || true)
else
s="${s:0:32}"
fi fi
if [ "${#h}" -ne 8 ]; then
echo "ERROR: cannot make the herdr name of '$n': no shasum, sha1sum, openssl or python3 to hash it" >&2
return 1
fi
s="${s:0:23}-${h}"
fi fi
printf '%s\n' "$s" printf '%s\n' "$s"
} }
@@ -155,8 +165,10 @@ _init_herdr_isolation() {
fi fi
local tmp_file="$wrapper_dir/herdr.tmp.$$.$RANDOM" local tmp_file="$wrapper_dir/herdr.tmp.$$.$RANDOM"
cat <<'EOF' > "$tmp_file" {
#!/usr/bin/env bash printf '%s\n' '#!/usr/bin/env bash'
declare -f _sanitize_herdr_agent_name
cat <<'EOF'
# Herdr-to-Herdr translation shim wrapper # Herdr-to-Herdr translation shim wrapper
set -euo pipefail set -euo pipefail
@@ -290,31 +302,6 @@ _real_herdr() {
fi fi
} }
_sanitize_herdr_agent_name() {
local n="$1"
if [ -z "$n" ]; then
printf 'agent\n'
return 0
fi
local s
s=$(echo "$n" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9_-]/-/g')
if [[ ! "$s" =~ ^[a-z] ]]; then s="x-$s"; fi
if [ "${#s}" -gt 32 ]; then
local h=""
if command -v shasum >/dev/null 2>&1; then
h=$(printf '%s' "$s" | shasum 2>/dev/null | awk '{print $1}' | cut -c 1-8)
elif command -v sha1sum >/dev/null 2>&1; then
h=$(printf '%s' "$s" | sha1sum 2>/dev/null | awk '{print $1}' | cut -c 1-8)
elif command -v openssl >/dev/null 2>&1; then
h=$(printf '%s' "$s" | openssl sha1 2>/dev/null | awk '{print $NF}' | cut -c 1-8)
else
h=$(python3 -c "import hashlib,sys; print(hashlib.sha1(sys.argv[1].encode()).hexdigest()[:8])" "$s" 2>/dev/null || echo "00000000")
fi
s="${s:0:23}-${h}"
fi
printf '%s\n' "${s:0:32}"
}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Workspace scoping (ISSUE-3). # Workspace scoping (ISSUE-3).
# #
@@ -441,7 +428,7 @@ sys.exit(1)
[ -n "$resolved_ws" ] && target_ws="$resolved_ws" [ -n "$resolved_ws" ] && target_ws="$resolved_ws"
fi fi
local sat pid="" local sat pid=""
sat=$(_sanitize_herdr_agent_name "$target") sat=$(_sanitize_herdr_agent_name "$target") || return 1
local cand get_ws local cand get_ws
if [ "$#" -ge 2 ]; then if [ "$#" -ge 2 ]; then
@@ -534,7 +521,7 @@ case "$cmd" in
_resolve_herdr_target() { _resolve_herdr_target() {
local raw="$1" local raw="$1"
local sat local sat
sat=$(_sanitize_herdr_agent_name "$raw") sat=$(_sanitize_herdr_agent_name "$raw") || return 1
if [ -n "$(_herdr_agent_get_scoped "$sat" 2>/dev/null || true)" ]; then if [ -n "$(_herdr_agent_get_scoped "$sat" 2>/dev/null || true)" ]; then
echo "$sat" echo "$sat"
return 0 return 0
@@ -604,6 +591,15 @@ sys.exit(1)
exit 1 exit 1
fi fi
_real_herdr agent read "$tgt" "$@" _real_herdr agent read "$tgt" "$@"
elif [ "$sub" = "attach" ] && [ $# -ge 1 ]; then
t="$1"
shift
tgt=$(_resolve_herdr_target "$t" || true)
if [ -z "$tgt" ]; then
echo "Error: no in-scope herdr agent for '$t'" >&2
exit 1
fi
_real_herdr agent attach "$tgt" "$@"
else else
_real_herdr agent "$sub" "$@" _real_herdr agent "$sub" "$@"
fi fi
@@ -623,19 +619,15 @@ sys.exit(1)
*) shift ;; *) shift ;;
esac esac
done done
if [ -n "$(_herdr_agent_get_scoped "$(_sanitize_herdr_agent_name "$sess")" 2>/dev/null || true)" ] \ ssess=$(_sanitize_herdr_agent_name "$sess") || exit 1
if [ -n "$(_herdr_agent_get_scoped "$ssess" 2>/dev/null || true)" ] \
|| [ -n "$(_herdr_agent_get_scoped "$sess" 2>/dev/null || true)" ]; then || [ -n "$(_herdr_agent_get_scoped "$sess" 2>/dev/null || true)" ]; then
exit 0 exit 0
fi fi
if _real_herdr agent list 2>/dev/null | TARGET_NAME="$sess" TARGET_WS="$(_herdr_ws_scope)" python3 -c ' if _real_herdr agent list 2>/dev/null | TARGET_NAME="$sess" TARGET_SAN="$ssess" TARGET_WS="$(_herdr_ws_scope)" python3 -c '
import sys, json, os import sys, json, os
try:
sys.path.insert(0, os.path.abspath(".agents/skills"))
from lib_py.agents.sanitize import sanitize_herdr_agent_name
except Exception:
sanitize_herdr_agent_name = lambda s: s.lower()[:32] if s else "agent"
tn = os.environ.get("TARGET_NAME", "") tn = os.environ.get("TARGET_NAME", "")
stn = sanitize_herdr_agent_name(tn) stn = os.environ.get("TARGET_SAN", "")
tws = os.environ.get("TARGET_WS", "") tws = os.environ.get("TARGET_WS", "")
try: try:
d = json.loads(sys.stdin.read()) d = json.loads(sys.stdin.read())
@@ -682,6 +674,7 @@ sys.exit(1)
*) run_cmd="$1"; shift ;; *) run_cmd="$1"; shift ;;
esac esac
done done
agent_name=$(_sanitize_herdr_agent_name "$name") || exit 1
if [ -z "$run_cmd" ]; then if [ -z "$run_cmd" ]; then
run_cmd="${SHELL:-/bin/bash}" run_cmd="${SHELL:-/bin/bash}"
fi fi
@@ -896,7 +889,6 @@ except Exception:
res="" res=""
success=0 success=0
backoffs=(0.5 1 2) backoffs=(0.5 1 2)
agent_name=$(_sanitize_herdr_agent_name "$name")
for i in $(seq 0 2); do for i in $(seq 0 2); do
if [ -n "$final_cmd" ]; then if [ -n "$final_cmd" ]; then
res=$(eval "_real_herdr agent start \"$agent_name\" --kind \"$kind\" --pane \"$target_pane\" -- $final_cmd" 2>&1 || true) res=$(eval "_real_herdr agent start \"$agent_name\" --kind \"$kind\" --pane \"$target_pane\" -- $final_cmd" 2>&1 || true)
@@ -967,7 +959,7 @@ except Exception:
# `herdr session list`), not individual agent panes — `session stop/delete # `herdr session list`), not individual agent panes — `session stop/delete
# "$sess"` with a MAM session name always fails (silently, via `|| true`). # "$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. # Resolve the real pane_id via `agent get` and close just that pane instead.
agent_target=$(_sanitize_herdr_agent_name "$sess") agent_target=$(_sanitize_herdr_agent_name "$sess") || exit 1
pane_id=$(_resolve_herdr_pane_id "$sess" 2>/dev/null || true) pane_id=$(_resolve_herdr_pane_id "$sess" 2>/dev/null || true)
if [ -n "$pane_id" ]; then if [ -n "$pane_id" ]; then
_real_herdr pane close "$pane_id" >/dev/null 2>&1 || true _real_herdr pane close "$pane_id" >/dev/null 2>&1 || true
@@ -1002,7 +994,7 @@ except Exception:
# there is no top-level "pane" key, and no "pid" field at all (only # 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 # "pane_id", herdr's own wN:pN identifier). The real OS pid requires a
# second call to `pane process-info --pane <pane_id>`. # second call to `pane process-info --pane <pane_id>`.
agent_target=$(_sanitize_herdr_agent_name "$sess") agent_target=$(_sanitize_herdr_agent_name "$sess") || exit 1
info=$(_real_herdr agent get "$agent_target" 2>/dev/null || _real_herdr agent get "$sess" 2>/dev/null || true) info=$(_real_herdr agent get "$agent_target" 2>/dev/null || _real_herdr agent get "$sess" 2>/dev/null || true)
pane_id="" cwd="" cmd="" pane_id="" cwd="" cmd=""
if [ -n "$info" ]; then if [ -n "$info" ]; then
@@ -1072,7 +1064,7 @@ except Exception:
*) shift ;; *) shift ;;
esac esac
done done
agent_target=$(_sanitize_herdr_agent_name "$sess") agent_target=$(_sanitize_herdr_agent_name "$sess") || exit 1
pane_id=$(_resolve_herdr_pane_id "$sess" 2>/dev/null || true) pane_id=$(_resolve_herdr_pane_id "$sess" 2>/dev/null || true)
if [ -n "$pane_id" ]; then if [ -n "$pane_id" ]; then
_real_herdr pane read "$pane_id" --source visible --lines 100 2>/dev/null \ _real_herdr pane read "$pane_id" --source visible --lines 100 2>/dev/null \
@@ -1103,7 +1095,7 @@ except Exception:
fi fi
# `pane send-keys` requires a real pane_id ("wN:pN"), not an agent name — # `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). # resolve it via `agent get` first (agent-level commands accept names).
agent_target=$(_sanitize_herdr_agent_name "$sess") agent_target=$(_sanitize_herdr_agent_name "$sess") || exit 1
pane_id=$(_resolve_herdr_pane_id "$sess" 2>/dev/null || true) pane_id=$(_resolve_herdr_pane_id "$sess" 2>/dev/null || true)
if [ -n "$pane_id" ]; then if [ -n "$pane_id" ]; then
_real_herdr pane send-keys "$pane_id" "$key" >/dev/null 2>&1 || true _real_herdr pane send-keys "$pane_id" "$key" >/dev/null 2>&1 || true
@@ -1387,6 +1379,7 @@ for name, pid in rows:
;; ;;
esac esac
EOF EOF
} > "$tmp_file"
chmod +x "$tmp_file" 2>/dev/null || true chmod +x "$tmp_file" 2>/dev/null || true
mv -f "$tmp_file" "$wrapper_dir/herdr" 2>/dev/null || rm -f "$tmp_file" 2>/dev/null || true mv -f "$tmp_file" "$wrapper_dir/herdr" 2>/dev/null || rm -f "$tmp_file" 2>/dev/null || true
if [[ ":$PATH:" != *":$wrapper_dir:"* ]]; then if [[ ":$PATH:" != *":$wrapper_dir:"* ]]; then
@@ -2787,7 +2780,7 @@ wait_for_agent_idle() {
fi fi
local target local target
target=$(_sanitize_herdr_agent_name "$sess") target=$(_sanitize_herdr_agent_name "$sess") || return 3
local raw_status="" agent_st="" local raw_status="" agent_st=""
raw_status="$(_sks_herdr agent get "$target" 2>/dev/null || _sks_herdr agent get "$sess" 2>/dev/null || true)" raw_status="$(_sks_herdr agent get "$target" 2>/dev/null || _sks_herdr agent get "$sess" 2>/dev/null || true)"
@@ -2912,7 +2905,7 @@ send_keys_safe() {
fi fi
local agent_target local agent_target
agent_target=$(_sanitize_herdr_agent_name "$sess") agent_target=$(_sanitize_herdr_agent_name "$sess") || return 1
# Busy gate: wait up to SKS_BUSY_TIMEOUT only if wait_for_agent_idle returned 3 (unknown) # Busy gate: wait up to SKS_BUSY_TIMEOUT only if wait_for_agent_idle returned 3 (unknown)
if [ "${_SKS_IDLE_RC:-0}" = "3" ]; then if [ "${_SKS_IDLE_RC:-0}" = "3" ]; then
@@ -895,8 +895,8 @@ finalize_unfinished() {
FINAL_JOB_STATUS="$final" FINAL_JOB_STATUS="$final"
if [ "$step" = "probe" ]; then if [ "$step" = "probe" ]; then
local sess="${target_sess#herdr:}" target_agent diag="no" local sess="${target_sess#herdr:}" target_agent diag="no"
target_agent=$(_sanitize_herdr_agent_name "$sess") target_agent=$(_sanitize_herdr_agent_name "$sess") || target_agent=""
_within_deadline "$deadline" _agent_still_working "$sess" "$target_agent" && diag="yes" [ -n "$target_agent" ] && _within_deadline "$deadline" _agent_still_working "$sess" "$target_agent" && diag="yes"
echo "liveness: no wire event for ${IDLE_TIMEOUT}s on job $job_id; herdr/pane says busy=$diag (diagnostic only, not evidence)" >&2 echo "liveness: no wire event for ${IDLE_TIMEOUT}s on job $job_id; herdr/pane says busy=$diag (diagnostic only, not evidence)" >&2
fi fi
# The answer comes back on fd 3: stdout carries registry.py's close output. # The answer comes back on fd 3: stdout carries registry.py's close output.