fix(lib.sh): one herdr name rule; the shim's agent attach resolves its target (Issue #71)
- One _sanitize_herdr_agent_name: the shim gets lib.sh's definition through `declare -f` instead of a second copy. Without shasum/sha1sum/openssl it hashes with python3 (same result as lib_py.agents.sanitize); with none of them it fails instead of truncating or appending 00000000. - Every caller stops on that failure: new-session before any pane is made, the lookups report failure, and has-session takes the name lib.sh computed (the cwd-relative import and its lower()[:32] fallback are gone). - `herdr agent attach <name>` through the shim resolves the target like get/read/prompt; options after the target are kept. Refs #71 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
29cc4fd904
commit
4243da507c
2 files changed
+45
-52
No files matched your search
+43
-50
@@ -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.
|
||||||
|
|||||||
Reference in new issue
Block a user