feat(herdr): complete Herdr 0.8.0 adaptation and sanitize module
This commit is contained in:
+33
-42
@@ -193,6 +193,12 @@ if cmd1 == "workspace":
|
||||
elif args[i] == "--cwd":
|
||||
cwd = args[i+1]
|
||||
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:
|
||||
i += 1
|
||||
workspaces = state.get("workspaces", [])
|
||||
@@ -250,7 +256,17 @@ elif cmd1 == "pane":
|
||||
print(json.dumps({"result": {"panes": panes_list}}))
|
||||
sys.exit(0)
|
||||
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"}}}))
|
||||
sys.exit(0)
|
||||
elif cmd2 == "layout":
|
||||
@@ -351,6 +367,7 @@ elif cmd1 == "agent":
|
||||
ws = ""
|
||||
cwd = ""
|
||||
pane = ""
|
||||
agent_type = ""
|
||||
# Find where -- is
|
||||
try:
|
||||
double_dash_idx = args.index("--")
|
||||
@@ -360,8 +377,8 @@ elif cmd1 == "agent":
|
||||
agent_cmd = []
|
||||
opts = args[3:]
|
||||
|
||||
# Whitelist herdr allowed flags (0.7.4 + 0.8.0)
|
||||
whitelist = {"--cwd", "--workspace", "--tab", "--split", "--env", "--focus", "--no-focus", "--kind", "--pane", "--timeout"}
|
||||
# Whitelist herdr 0.8.0 allowed flags for agent start
|
||||
whitelist = {"--kind", "--pane", "--timeout"}
|
||||
i = 0
|
||||
unknown_flags = []
|
||||
while i < len(opts):
|
||||
@@ -370,13 +387,7 @@ elif cmd1 == "agent":
|
||||
unknown_flags.append(opt)
|
||||
i += 1
|
||||
continue
|
||||
if opt == "--workspace":
|
||||
ws = opts[i+1]
|
||||
i += 2
|
||||
elif opt == "--cwd":
|
||||
cwd = opts[i+1]
|
||||
i += 2
|
||||
elif opt == "--kind":
|
||||
if opt == "--kind":
|
||||
agent_type = opts[i+1]
|
||||
i += 2
|
||||
elif opt == "--pane":
|
||||
@@ -384,42 +395,22 @@ elif cmd1 == "agent":
|
||||
i += 2
|
||||
elif opt == "--timeout":
|
||||
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:
|
||||
i += 1
|
||||
|
||||
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.exit(0)
|
||||
|
||||
# Determine the agent type (claude, agy, hermes, cline)
|
||||
agent_type = "claude"
|
||||
if agent_cmd:
|
||||
if "claude" in agent_cmd[0]:
|
||||
agent_type = "claude"
|
||||
elif "agy" in agent_cmd[0]:
|
||||
agent_type = "agy"
|
||||
elif "hermes" in agent_cmd[0]:
|
||||
agent_type = "hermes"
|
||||
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"
|
||||
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(1)
|
||||
|
||||
if not agent_type or not pane:
|
||||
sys.stderr.write("error: the following required arguments were not provided:\\\\n --kind <KIND>\\\\n --pane <ID>\\\\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Name validation: ^[a-z][a-z0-9_-]{0,31}$
|
||||
import re
|
||||
if not re.match(r'^[a-z][a-z0-9_-]{0,31}$', name):
|
||||
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)
|
||||
sys.exit(1)
|
||||
|
||||
# TUI Welcome Tokens definition to prevent TUI readiness check timeout
|
||||
buffer_content = {
|
||||
|
||||
@@ -12,7 +12,7 @@ import pytest
|
||||
def _create_mock_agent_session(mam_sandbox):
|
||||
"""Helper to register an active agent session in mock herdr."""
|
||||
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))
|
||||
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ def test_h1_to_h8_shim_contract(mam_sandbox, mock_herdr, mock_agents):
|
||||
tmp_path = mam_sandbox
|
||||
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"""
|
||||
source {lib_path}
|
||||
_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))
|
||||
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"]
|
||||
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:
|
||||
try:
|
||||
dd_idx = call.index("--")
|
||||
@@ -49,14 +50,14 @@ def test_h1_to_h8_shim_contract(mam_sandbox, mock_herdr, mock_agents):
|
||||
opts = call[3:]
|
||||
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)):
|
||||
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
|
||||
if argv:
|
||||
assert argv[0] == "/usr/bin/python3", f"Executable path mutated: {argv[0]}"
|
||||
assert "--kind" in opts, "Missing required --kind in agent start"
|
||||
assert "--pane" in opts, "Missing required --pane in agent start"
|
||||
|
||||
def test_h9_mock_response_contract_schema(mock_herdr):
|
||||
"""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
|
||||
export MAM_MIN_PANE_COLS=60
|
||||
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))
|
||||
assert res.returncode == 0, f"Stderr: {res.stderr}"
|
||||
@@ -111,7 +112,7 @@ def test_h14_mock_concurrency_lock_invariant(mock_herdr):
|
||||
import subprocess
|
||||
procs = []
|
||||
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)
|
||||
for p in procs:
|
||||
p.wait()
|
||||
|
||||
Reference in New Issue
Block a user