feat(herdr): complete Herdr 0.8.0 adaptation and sanitize module
This commit is contained in:
+37
-28
@@ -28,14 +28,19 @@ AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.
|
||||
|
||||
_sanitize_herdr_agent_name() {
|
||||
local n="$1"
|
||||
if [ "${#n}" -le 32 ]; then
|
||||
printf '%s\n' "$n"
|
||||
if [ -z "$n" ]; then
|
||||
printf 'agent\n'
|
||||
return 0
|
||||
fi
|
||||
local p="${n:0:16}"
|
||||
local s="${n: -15}"
|
||||
local res="${p}-${s}"
|
||||
printf '%s\n' "${res:0:32}"
|
||||
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 p="${s:0:16}"
|
||||
local suf="${s: -15}"
|
||||
s="${p}-${suf}"
|
||||
fi
|
||||
printf '%s\n' "${s:0:32}"
|
||||
}
|
||||
|
||||
# Add common Homebrew and local binary paths to PATH to ensure they are available in non-interactive shells
|
||||
@@ -202,17 +207,20 @@ _real_herdr() {
|
||||
|
||||
_sanitize_herdr_agent_name() {
|
||||
local n="$1"
|
||||
if [ "${#n}" -le 32 ]; then
|
||||
printf '%s\n' "$n"
|
||||
if [ -z "$n" ]; then
|
||||
printf 'agent\n'
|
||||
return 0
|
||||
fi
|
||||
local p="${n:0:16}"
|
||||
local s="${n: -15}"
|
||||
local res="${p}-${s}"
|
||||
printf '%s\n' "${res:0:32}"
|
||||
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 p="${s:0:16}"
|
||||
local suf="${s: -15}"
|
||||
s="${p}-${suf}"
|
||||
fi
|
||||
printf '%s\n' "${s:0:32}"
|
||||
}
|
||||
|
||||
wrapper_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
cmd="${1:-}"
|
||||
if [ -z "$cmd" ]; then
|
||||
echo "herdr shim: no command specified" >&2
|
||||
@@ -434,7 +442,7 @@ except Exception:
|
||||
fi
|
||||
|
||||
if [ "$split_dir" = "right" ] || [ "$split_dir" = "down" ]; then
|
||||
split_json=$(_real_herdr pane split --pane "$sample_pane" --direction "$split_dir" --cwd "${ws:-.}" --no-focus 2>/dev/null || echo "")
|
||||
split_json=$(_real_herdr pane split --pane "$sample_pane" --direction "$split_dir" --cwd "${ws:-.}" $env_flags --no-focus 2>/dev/null || echo "")
|
||||
target_pane=$(echo "$split_json" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
@@ -448,7 +456,7 @@ except Exception:
|
||||
# W2b: Overflow threshold reached — force create fresh workspace
|
||||
existing_ws=""
|
||||
else
|
||||
split_json=$(_real_herdr pane split --pane "$sample_pane" --direction right --cwd "${ws:-.}" --no-focus 2>/dev/null || echo "")
|
||||
split_json=$(_real_herdr pane split --pane "$sample_pane" --direction right --cwd "${ws:-.}" $env_flags --no-focus 2>/dev/null || echo "")
|
||||
target_pane=$(echo "$split_json" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
@@ -462,7 +470,7 @@ except Exception:
|
||||
fi
|
||||
|
||||
if [ -z "$existing_ws" ] || [ -z "$target_pane" ]; then
|
||||
ws_json=$(_real_herdr workspace create --cwd "${ws:-.}" --no-focus 2>/dev/null || echo "")
|
||||
ws_json=$(_real_herdr workspace create --cwd "${ws:-.}" $env_flags --no-focus 2>/dev/null || echo "")
|
||||
target_pane=$(echo "$ws_json" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
@@ -475,24 +483,25 @@ except Exception:
|
||||
" 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
if [ -z "$target_pane" ]; then
|
||||
echo "Error: target_pane allocation failed for $name" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$kind" ]; then
|
||||
echo "Error: unable to determine agent kind for session $name" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# W5/W6: Backoff retries (0.5 -> 1 -> 2), abort immediately on usage or unknown flag errors
|
||||
res=""
|
||||
success=0
|
||||
backoffs=(0.5 1 2)
|
||||
agent_name=$(_sanitize_herdr_agent_name "$name")
|
||||
for i in $(seq 0 2); do
|
||||
if [ -n "$target_pane" ]; then
|
||||
if [ -n "$final_cmd" ]; then
|
||||
res=$(eval "_real_herdr agent start \"$agent_name\" --kind \"${kind:-claude}\" --pane \"$target_pane\" $env_flags -- $final_cmd" 2>&1 || true)
|
||||
else
|
||||
res=$(eval "_real_herdr agent start \"$agent_name\" --kind \"${kind:-claude}\" --pane \"$target_pane\" $env_flags" 2>&1 || true)
|
||||
fi
|
||||
if [ -n "$final_cmd" ]; then
|
||||
res=$(eval "_real_herdr agent start \"$agent_name\" --kind \"$kind\" --pane \"$target_pane\" -- $final_cmd" 2>&1 || true)
|
||||
else
|
||||
if [ -n "$final_cmd" ]; then
|
||||
res=$(eval "_real_herdr agent start \"$agent_name\" --kind \"${kind:-claude}\" $env_flags -- $final_cmd" 2>&1 || true)
|
||||
else
|
||||
res=$(eval "_real_herdr agent start \"$agent_name\" --kind \"${kind:-claude}\" $env_flags" 2>&1 || true)
|
||||
fi
|
||||
res=$(eval "_real_herdr agent start \"$agent_name\" --kind \"$kind\" --pane \"$target_pane\"" 2>&1 || true)
|
||||
fi
|
||||
if echo "$res" | grep -q "agent_started"; then
|
||||
success=1
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# sanitize.py — Unified agent name sanitization contract for herdr 0.8.0 (E3 & E4)
|
||||
|
||||
import re
|
||||
|
||||
def sanitize_herdr_agent_name(name: str) -> str:
|
||||
"""
|
||||
Sanitizes an agent name according to herdr 0.8.0 rules:
|
||||
1. Convert to lowercase.
|
||||
2. Replace invalid characters ([^a-z0-9_-]) with '-'.
|
||||
3. Ensure starts with a lowercase letter [a-z], prefixing with 'x-' if needed.
|
||||
4. Truncate to maximum 32 characters (16 + '-' + 15).
|
||||
"""
|
||||
if not name:
|
||||
return "agent"
|
||||
|
||||
# 1. Lowercase
|
||||
s = str(name).lower()
|
||||
|
||||
# 2. Replace illegal characters with '-'
|
||||
s = re.sub(r'[^a-z0-9_-]', '-', s)
|
||||
|
||||
# 3. Ensure starts with a letter [a-z]
|
||||
if not s or not s[0].isalpha():
|
||||
s = "x-" + s
|
||||
|
||||
# 4. Truncate to 32 chars if needed
|
||||
if len(s) > 32:
|
||||
s = s[:16] + '-' + s[-15:]
|
||||
|
||||
return s
|
||||
@@ -458,10 +458,7 @@ def _pin_and_verify_resume(s, agent, cwd, uuid, degraded=False):
|
||||
actions.append(f"updated {id_name} id: {uuid}")
|
||||
|
||||
|
||||
def _sanitize(n):
|
||||
if len(n) <= 32:
|
||||
return n
|
||||
return f"{n[:16]}-{n[-15:]}"[:32]
|
||||
from lib_py.agents.sanitize import sanitize_herdr_agent_name as _sanitize
|
||||
|
||||
yaml_sessions = d.get('herdr_sessions', [])
|
||||
yaml_session_names = {s['name'] for s in yaml_sessions if s.get('name')}
|
||||
|
||||
@@ -122,21 +122,10 @@ def get_job_status(s):
|
||||
|
||||
|
||||
sessions_detail = []
|
||||
def _sanitize(n):
|
||||
if len(n) <= 32:
|
||||
return n
|
||||
return f"{n[:16]}-{n[-15:]}"[:32]
|
||||
from lib_py.agents.sanitize import sanitize_herdr_agent_name as _sanitize
|
||||
|
||||
def is_alive(name, server):
|
||||
if f"{name}|{server}" in alive or f"{_sanitize(name)}|{server}" in alive:
|
||||
return True
|
||||
prefix = name[:14]
|
||||
suffix = name[-12:]
|
||||
for item in alive:
|
||||
a_name, a_srv = item.split("|", 1) if "|" in item else (item, "default")
|
||||
if a_srv == server and a_name.startswith(prefix) and a_name.endswith(suffix):
|
||||
return True
|
||||
return False
|
||||
return f"{name}|{server}" in alive or f"{_sanitize(name)}|{server}" in alive
|
||||
|
||||
for s in d.get('herdr_sessions', []):
|
||||
name = s.get('name', '?')
|
||||
@@ -235,10 +224,7 @@ def get_job_status(s):
|
||||
return (jid, 'unknown')
|
||||
|
||||
|
||||
def _sanitize(n):
|
||||
if len(n) <= 32:
|
||||
return n
|
||||
return f"{n[:16]}-{n[-15:]}"[:32]
|
||||
from lib_py.agents.sanitize import sanitize_herdr_agent_name as _sanitize
|
||||
|
||||
sessions = d.get('herdr_sessions', [])
|
||||
print(f"agent-sessions status — {drift['timestamp']} (herdr_confirmed={drift['herdr_confirmed']})")
|
||||
@@ -248,15 +234,7 @@ print("-" * 136)
|
||||
if not sessions:
|
||||
print("(no sessions registered)")
|
||||
def is_alive(name, server):
|
||||
if f"{name}|{server}" in alive or f"{_sanitize(name)}|{server}" in alive:
|
||||
return True
|
||||
prefix = name[:14]
|
||||
suffix = name[-12:]
|
||||
for item in alive:
|
||||
a_name, a_srv = item.split("|", 1) if "|" in item else (item, "default")
|
||||
if a_srv == server and a_name.startswith(prefix) and a_name.endswith(suffix):
|
||||
return True
|
||||
return False
|
||||
return f"{name}|{server}" in alive or f"{_sanitize(name)}|{server}" in alive
|
||||
|
||||
for s in sessions:
|
||||
name = s.get('name', '?')
|
||||
|
||||
Reference in New Issue
Block a user