feat(herdr): complete Herdr 0.8.0 adaptation and sanitize module

This commit is contained in:
2026-08-14 23:09:08 +09:00
parent 1c24732be0
commit 301ff5bb1f
7 changed files with 117 additions and 111 deletions
+37 -28
View File
@@ -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
+30
View File
@@ -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', '?')