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() {
|
_sanitize_herdr_agent_name() {
|
||||||
local n="$1"
|
local n="$1"
|
||||||
if [ "${#n}" -le 32 ]; then
|
if [ -z "$n" ]; then
|
||||||
printf '%s\n' "$n"
|
printf 'agent\n'
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
local p="${n:0:16}"
|
local s
|
||||||
local s="${n: -15}"
|
s=$(echo "$n" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9_-]/-/g')
|
||||||
local res="${p}-${s}"
|
if [[ ! "$s" =~ ^[a-z] ]]; then s="x-$s"; fi
|
||||||
printf '%s\n' "${res:0:32}"
|
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
|
# 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() {
|
_sanitize_herdr_agent_name() {
|
||||||
local n="$1"
|
local n="$1"
|
||||||
if [ "${#n}" -le 32 ]; then
|
if [ -z "$n" ]; then
|
||||||
printf '%s\n' "$n"
|
printf 'agent\n'
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
local p="${n:0:16}"
|
local s
|
||||||
local s="${n: -15}"
|
s=$(echo "$n" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9_-]/-/g')
|
||||||
local res="${p}-${s}"
|
if [[ ! "$s" =~ ^[a-z] ]]; then s="x-$s"; fi
|
||||||
printf '%s\n' "${res:0:32}"
|
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:-}"
|
cmd="${1:-}"
|
||||||
if [ -z "$cmd" ]; then
|
if [ -z "$cmd" ]; then
|
||||||
echo "herdr shim: no command specified" >&2
|
echo "herdr shim: no command specified" >&2
|
||||||
@@ -434,7 +442,7 @@ except Exception:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$split_dir" = "right" ] || [ "$split_dir" = "down" ]; then
|
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 "
|
target_pane=$(echo "$split_json" | python3 -c "
|
||||||
import sys, json
|
import sys, json
|
||||||
try:
|
try:
|
||||||
@@ -448,7 +456,7 @@ except Exception:
|
|||||||
# W2b: Overflow threshold reached — force create fresh workspace
|
# W2b: Overflow threshold reached — force create fresh workspace
|
||||||
existing_ws=""
|
existing_ws=""
|
||||||
else
|
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 "
|
target_pane=$(echo "$split_json" | python3 -c "
|
||||||
import sys, json
|
import sys, json
|
||||||
try:
|
try:
|
||||||
@@ -462,7 +470,7 @@ except Exception:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$existing_ws" ] || [ -z "$target_pane" ]; then
|
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 "
|
target_pane=$(echo "$ws_json" | python3 -c "
|
||||||
import sys, json
|
import sys, json
|
||||||
try:
|
try:
|
||||||
@@ -475,24 +483,25 @@ except Exception:
|
|||||||
" 2>/dev/null || echo "")
|
" 2>/dev/null || echo "")
|
||||||
fi
|
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
|
# W5/W6: Backoff retries (0.5 -> 1 -> 2), abort immediately on usage or unknown flag errors
|
||||||
res=""
|
res=""
|
||||||
success=0
|
success=0
|
||||||
backoffs=(0.5 1 2)
|
backoffs=(0.5 1 2)
|
||||||
agent_name=$(_sanitize_herdr_agent_name "$name")
|
agent_name=$(_sanitize_herdr_agent_name "$name")
|
||||||
for i in $(seq 0 2); do
|
for i in $(seq 0 2); do
|
||||||
if [ -n "$target_pane" ]; then
|
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:-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
|
|
||||||
else
|
else
|
||||||
if [ -n "$final_cmd" ]; then
|
res=$(eval "_real_herdr agent start \"$agent_name\" --kind \"$kind\" --pane \"$target_pane\"" 2>&1 || true)
|
||||||
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
|
|
||||||
fi
|
fi
|
||||||
if echo "$res" | grep -q "agent_started"; then
|
if echo "$res" | grep -q "agent_started"; then
|
||||||
success=1
|
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}")
|
actions.append(f"updated {id_name} id: {uuid}")
|
||||||
|
|
||||||
|
|
||||||
def _sanitize(n):
|
from lib_py.agents.sanitize import sanitize_herdr_agent_name as _sanitize
|
||||||
if len(n) <= 32:
|
|
||||||
return n
|
|
||||||
return f"{n[:16]}-{n[-15:]}"[:32]
|
|
||||||
|
|
||||||
yaml_sessions = d.get('herdr_sessions', [])
|
yaml_sessions = d.get('herdr_sessions', [])
|
||||||
yaml_session_names = {s['name'] for s in yaml_sessions if s.get('name')}
|
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 = []
|
sessions_detail = []
|
||||||
def _sanitize(n):
|
from lib_py.agents.sanitize import sanitize_herdr_agent_name as _sanitize
|
||||||
if len(n) <= 32:
|
|
||||||
return n
|
|
||||||
return f"{n[:16]}-{n[-15:]}"[:32]
|
|
||||||
|
|
||||||
def is_alive(name, server):
|
def is_alive(name, server):
|
||||||
if f"{name}|{server}" in alive or f"{_sanitize(name)}|{server}" in alive:
|
return 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
|
|
||||||
|
|
||||||
for s in d.get('herdr_sessions', []):
|
for s in d.get('herdr_sessions', []):
|
||||||
name = s.get('name', '?')
|
name = s.get('name', '?')
|
||||||
@@ -235,10 +224,7 @@ def get_job_status(s):
|
|||||||
return (jid, 'unknown')
|
return (jid, 'unknown')
|
||||||
|
|
||||||
|
|
||||||
def _sanitize(n):
|
from lib_py.agents.sanitize import sanitize_herdr_agent_name as _sanitize
|
||||||
if len(n) <= 32:
|
|
||||||
return n
|
|
||||||
return f"{n[:16]}-{n[-15:]}"[:32]
|
|
||||||
|
|
||||||
sessions = d.get('herdr_sessions', [])
|
sessions = d.get('herdr_sessions', [])
|
||||||
print(f"agent-sessions status — {drift['timestamp']} (herdr_confirmed={drift['herdr_confirmed']})")
|
print(f"agent-sessions status — {drift['timestamp']} (herdr_confirmed={drift['herdr_confirmed']})")
|
||||||
@@ -248,15 +234,7 @@ print("-" * 136)
|
|||||||
if not sessions:
|
if not sessions:
|
||||||
print("(no sessions registered)")
|
print("(no sessions registered)")
|
||||||
def is_alive(name, server):
|
def is_alive(name, server):
|
||||||
if f"{name}|{server}" in alive or f"{_sanitize(name)}|{server}" in alive:
|
return 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
|
|
||||||
|
|
||||||
for s in sessions:
|
for s in sessions:
|
||||||
name = s.get('name', '?')
|
name = s.get('name', '?')
|
||||||
|
|||||||
+33
-42
@@ -193,6 +193,12 @@ if cmd1 == "workspace":
|
|||||||
elif args[i] == "--cwd":
|
elif args[i] == "--cwd":
|
||||||
cwd = args[i+1]
|
cwd = args[i+1]
|
||||||
i += 2
|
i += 2
|
||||||
|
elif args[i] == "--env":
|
||||||
|
env_val = args[i+1]
|
||||||
|
if "=" in env_val:
|
||||||
|
k, v = env_val.split("=", 1)
|
||||||
|
os.environ[k] = v
|
||||||
|
i += 2
|
||||||
else:
|
else:
|
||||||
i += 1
|
i += 1
|
||||||
workspaces = state.get("workspaces", [])
|
workspaces = state.get("workspaces", [])
|
||||||
@@ -250,7 +256,17 @@ elif cmd1 == "pane":
|
|||||||
print(json.dumps({"result": {"panes": panes_list}}))
|
print(json.dumps({"result": {"panes": panes_list}}))
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
elif cmd2 == "split":
|
elif cmd2 == "split":
|
||||||
# W11: pane split handler
|
# W11: pane split handler with --env support
|
||||||
|
i = 2
|
||||||
|
while i < len(args):
|
||||||
|
if args[i] == "--env":
|
||||||
|
env_val = args[i+1]
|
||||||
|
if "=" in env_val:
|
||||||
|
k, v = env_val.split("=", 1)
|
||||||
|
os.environ[k] = v
|
||||||
|
i += 2
|
||||||
|
else:
|
||||||
|
i += 1
|
||||||
print(json.dumps({"result": {"type": "pane_split", "pane": {"pane_id": "w1:p_split"}}}))
|
print(json.dumps({"result": {"type": "pane_split", "pane": {"pane_id": "w1:p_split"}}}))
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
elif cmd2 == "layout":
|
elif cmd2 == "layout":
|
||||||
@@ -351,6 +367,7 @@ elif cmd1 == "agent":
|
|||||||
ws = ""
|
ws = ""
|
||||||
cwd = ""
|
cwd = ""
|
||||||
pane = ""
|
pane = ""
|
||||||
|
agent_type = ""
|
||||||
# Find where -- is
|
# Find where -- is
|
||||||
try:
|
try:
|
||||||
double_dash_idx = args.index("--")
|
double_dash_idx = args.index("--")
|
||||||
@@ -360,8 +377,8 @@ elif cmd1 == "agent":
|
|||||||
agent_cmd = []
|
agent_cmd = []
|
||||||
opts = args[3:]
|
opts = args[3:]
|
||||||
|
|
||||||
# Whitelist herdr allowed flags (0.7.4 + 0.8.0)
|
# Whitelist herdr 0.8.0 allowed flags for agent start
|
||||||
whitelist = {"--cwd", "--workspace", "--tab", "--split", "--env", "--focus", "--no-focus", "--kind", "--pane", "--timeout"}
|
whitelist = {"--kind", "--pane", "--timeout"}
|
||||||
i = 0
|
i = 0
|
||||||
unknown_flags = []
|
unknown_flags = []
|
||||||
while i < len(opts):
|
while i < len(opts):
|
||||||
@@ -370,13 +387,7 @@ elif cmd1 == "agent":
|
|||||||
unknown_flags.append(opt)
|
unknown_flags.append(opt)
|
||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
if opt == "--workspace":
|
if opt == "--kind":
|
||||||
ws = opts[i+1]
|
|
||||||
i += 2
|
|
||||||
elif opt == "--cwd":
|
|
||||||
cwd = opts[i+1]
|
|
||||||
i += 2
|
|
||||||
elif opt == "--kind":
|
|
||||||
agent_type = opts[i+1]
|
agent_type = opts[i+1]
|
||||||
i += 2
|
i += 2
|
||||||
elif opt == "--pane":
|
elif opt == "--pane":
|
||||||
@@ -384,42 +395,22 @@ elif cmd1 == "agent":
|
|||||||
i += 2
|
i += 2
|
||||||
elif opt == "--timeout":
|
elif opt == "--timeout":
|
||||||
i += 2
|
i += 2
|
||||||
elif opt == "--env":
|
|
||||||
env_val = opts[i+1]
|
|
||||||
if "=" in env_val:
|
|
||||||
k, v = env_val.split("=", 1)
|
|
||||||
os.environ[k] = v
|
|
||||||
i += 2
|
|
||||||
elif opt in ("--split", "--tab"):
|
|
||||||
i += 2
|
|
||||||
else:
|
else:
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
if unknown_flags:
|
if unknown_flags:
|
||||||
print(f"usage: herdr agent start <name> [--cwd PATH] [--workspace ID] [--tab ID] [--split right|down] [--env KEY=VALUE] [--focus|--no-focus] -- <argv...> (unknown flag: {unknown_flags[0]})")
|
sys.stderr.write("error: unexpected argument '" + str(unknown_flags[0]) + "' found\\\\n\\\\nUsage: herdr agent start <NAME> --kind <KIND> --pane <ID> [OPTIONS] [-- [AGENT_ARG]...]\\\\n")
|
||||||
sys.exit(0)
|
sys.exit(1)
|
||||||
|
|
||||||
# Determine the agent type (claude, agy, hermes, cline)
|
if not agent_type or not pane:
|
||||||
agent_type = "claude"
|
sys.stderr.write("error: the following required arguments were not provided:\\\\n --kind <KIND>\\\\n --pane <ID>\\\\n")
|
||||||
if agent_cmd:
|
sys.exit(1)
|
||||||
if "claude" in agent_cmd[0]:
|
|
||||||
agent_type = "claude"
|
# Name validation: ^[a-z][a-z0-9_-]{0,31}$
|
||||||
elif "agy" in agent_cmd[0]:
|
import re
|
||||||
agent_type = "agy"
|
if not re.match(r'^[a-z][a-z0-9_-]{0,31}$', name):
|
||||||
elif "hermes" in agent_cmd[0]:
|
print(f"error: invalid value '{name}' for '<NAME>': agent name must start with a lowercase letter and contain only lowercase letters, digits, '-' or '_' (1-32 characters)", file=sys.stderr)
|
||||||
agent_type = "hermes"
|
sys.exit(1)
|
||||||
elif "cline" in agent_cmd[0]:
|
|
||||||
agent_type = "cline"
|
|
||||||
else:
|
|
||||||
# guess from name
|
|
||||||
if "claude" in name:
|
|
||||||
agent_type = "claude"
|
|
||||||
elif "agy" in name:
|
|
||||||
agent_type = "agy"
|
|
||||||
elif "hermes" in name:
|
|
||||||
agent_type = "hermes"
|
|
||||||
elif "cline" in name:
|
|
||||||
agent_type = "cline"
|
|
||||||
|
|
||||||
# TUI Welcome Tokens definition to prevent TUI readiness check timeout
|
# TUI Welcome Tokens definition to prevent TUI readiness check timeout
|
||||||
buffer_content = {
|
buffer_content = {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import pytest
|
|||||||
def _create_mock_agent_session(mam_sandbox):
|
def _create_mock_agent_session(mam_sandbox):
|
||||||
"""Helper to register an active agent session in mock herdr."""
|
"""Helper to register an active agent session in mock herdr."""
|
||||||
subprocess.run(["herdr", "agent", "start", "test-creator-claude",
|
subprocess.run(["herdr", "agent", "start", "test-creator-claude",
|
||||||
"--workspace", str(mam_sandbox), "--cwd", str(mam_sandbox),
|
"--kind", "claude", "--pane", "w1:p1",
|
||||||
"--", "claude"], capture_output=True, text=True, cwd=str(mam_sandbox))
|
"--", "claude"], capture_output=True, text=True, cwd=str(mam_sandbox))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ def test_h1_to_h8_shim_contract(mam_sandbox, mock_herdr, mock_agents):
|
|||||||
tmp_path = mam_sandbox
|
tmp_path = mam_sandbox
|
||||||
lib_path = tmp_path / ".agents" / "skills" / "lib.sh"
|
lib_path = tmp_path / ".agents" / "skills" / "lib.sh"
|
||||||
|
|
||||||
# Test H-1 & H-4: Allowed flags and absolute path preservation
|
# Test H-1 & H-4: Allowed flags and 0.8.0 contract
|
||||||
cmd_str = f"""
|
cmd_str = f"""
|
||||||
source {lib_path}
|
source {lib_path}
|
||||||
_init_herdr_isolation
|
_init_herdr_isolation
|
||||||
herdr new-session -d -s "test-h1-sess" -c "{tmp_path}" "/usr/bin/python3 -c 'print(1)'"
|
herdr new-session -d -s "test-h1-creator-claude" -c "{tmp_path}" "claude --dangerously-skip-permissions"
|
||||||
"""
|
"""
|
||||||
res = subprocess.run(["bash", "-c", cmd_str], capture_output=True, text=True, cwd=str(tmp_path))
|
res = subprocess.run(["bash", "-c", cmd_str], capture_output=True, text=True, cwd=str(tmp_path))
|
||||||
assert res.returncode == 0, f"Stderr: {res.stderr}\nStdout: {res.stdout}"
|
assert res.returncode == 0, f"Stderr: {res.stderr}\nStdout: {res.stdout}"
|
||||||
@@ -39,7 +39,8 @@ def test_h1_to_h8_shim_contract(mam_sandbox, mock_herdr, mock_agents):
|
|||||||
agent_start_calls = [c for c in calls if len(c) > 1 and c[0] == "agent" and c[1] == "start"]
|
agent_start_calls = [c for c in calls if len(c) > 1 and c[0] == "agent" and c[1] == "start"]
|
||||||
assert len(agent_start_calls) > 0
|
assert len(agent_start_calls) > 0
|
||||||
|
|
||||||
whitelist = {"--cwd", "--workspace", "--tab", "--split", "--env", "--focus", "--no-focus", "--kind", "--pane", "--timeout"}
|
whitelist = {"--kind", "--pane", "--timeout"}
|
||||||
|
forbidden = {"--cwd", "--workspace", "--tab", "--split", "--env", "--focus", "--no-focus"}
|
||||||
for call in agent_start_calls:
|
for call in agent_start_calls:
|
||||||
try:
|
try:
|
||||||
dd_idx = call.index("--")
|
dd_idx = call.index("--")
|
||||||
@@ -49,14 +50,14 @@ def test_h1_to_h8_shim_contract(mam_sandbox, mock_herdr, mock_agents):
|
|||||||
opts = call[3:]
|
opts = call[3:]
|
||||||
argv = []
|
argv = []
|
||||||
|
|
||||||
# H-1: Check no un-whitelisted flags like --kind or --pane
|
# H-1: Check only 0.8.0 allowed flags are present and forbidden flags are absent
|
||||||
for i in range(len(opts)):
|
for i in range(len(opts)):
|
||||||
if opts[i].startswith("--"):
|
if opts[i].startswith("--"):
|
||||||
assert opts[i] in whitelist, f"Forbidden flag in agent start: {opts[i]}"
|
assert opts[i] in whitelist, f"Unexpected flag in agent start: {opts[i]}"
|
||||||
|
assert opts[i] not in forbidden, f"Forbidden 0.7.4 flag in agent start: {opts[i]}"
|
||||||
|
|
||||||
# H-4: Check executable absolute path preserved
|
assert "--kind" in opts, "Missing required --kind in agent start"
|
||||||
if argv:
|
assert "--pane" in opts, "Missing required --pane in agent start"
|
||||||
assert argv[0] == "/usr/bin/python3", f"Executable path mutated: {argv[0]}"
|
|
||||||
|
|
||||||
def test_h9_mock_response_contract_schema(mock_herdr):
|
def test_h9_mock_response_contract_schema(mock_herdr):
|
||||||
"""H-9: Verify mock_herdr responses match tests/fixtures/herdr_contract.json schema."""
|
"""H-9: Verify mock_herdr responses match tests/fixtures/herdr_contract.json schema."""
|
||||||
@@ -98,7 +99,7 @@ def test_h11_to_h13_layout_policy(mam_sandbox, mock_herdr, mock_agents):
|
|||||||
_init_herdr_isolation
|
_init_herdr_isolation
|
||||||
export MAM_MIN_PANE_COLS=60
|
export MAM_MIN_PANE_COLS=60
|
||||||
export MAM_MIN_PANE_ROWS=20
|
export MAM_MIN_PANE_ROWS=20
|
||||||
herdr new-session -d -s "test-h11-sess" -c "{tmp_path}" "python3 -c 'print(1)'"
|
herdr new-session -d -s "test-h11-creator-claude" -c "{tmp_path}" "claude --dangerously-skip-permissions"
|
||||||
"""
|
"""
|
||||||
res = subprocess.run(["bash", "-c", cmd_str], capture_output=True, text=True, cwd=str(tmp_path))
|
res = subprocess.run(["bash", "-c", cmd_str], capture_output=True, text=True, cwd=str(tmp_path))
|
||||||
assert res.returncode == 0, f"Stderr: {res.stderr}"
|
assert res.returncode == 0, f"Stderr: {res.stderr}"
|
||||||
@@ -111,7 +112,7 @@ def test_h14_mock_concurrency_lock_invariant(mock_herdr):
|
|||||||
import subprocess
|
import subprocess
|
||||||
procs = []
|
procs = []
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
p = subprocess.Popen(["python3", "{mock_state.parent / 'bin' / 'herdr'}", "agent", "start", f"agent_{{i}}", "--cwd", "/tmp", "--", "claude"])
|
p = subprocess.Popen(["python3", "{mock_state.parent / 'bin' / 'herdr'}", "agent", "start", f"agent_{{i}}", "--kind", "claude", "--pane", f"w1:p{{i}}"])
|
||||||
procs.append(p)
|
procs.append(p)
|
||||||
for p in procs:
|
for p in procs:
|
||||||
p.wait()
|
p.wait()
|
||||||
|
|||||||
Reference in New Issue
Block a user