fix(herdr): support Herdr 0.8.0 pane allocation, agent prompt, and length limits
This commit is contained in:
+77
-19
@@ -350,6 +350,7 @@ elif cmd1 == "agent":
|
||||
name = args[2]
|
||||
ws = ""
|
||||
cwd = ""
|
||||
pane = ""
|
||||
# Find where -- is
|
||||
try:
|
||||
double_dash_idx = args.index("--")
|
||||
@@ -359,8 +360,8 @@ elif cmd1 == "agent":
|
||||
agent_cmd = []
|
||||
opts = args[3:]
|
||||
|
||||
# W8: Whitelist herdr 0.7.4 allowed flags
|
||||
whitelist = {"--cwd", "--workspace", "--tab", "--split", "--env", "--focus", "--no-focus"}
|
||||
# Whitelist herdr allowed flags (0.7.4 + 0.8.0)
|
||||
whitelist = {"--cwd", "--workspace", "--tab", "--split", "--env", "--focus", "--no-focus", "--kind", "--pane", "--timeout"}
|
||||
i = 0
|
||||
unknown_flags = []
|
||||
while i < len(opts):
|
||||
@@ -375,6 +376,14 @@ elif cmd1 == "agent":
|
||||
elif opt == "--cwd":
|
||||
cwd = opts[i+1]
|
||||
i += 2
|
||||
elif opt == "--kind":
|
||||
agent_type = opts[i+1]
|
||||
i += 2
|
||||
elif opt == "--pane":
|
||||
pane = opts[i+1]
|
||||
i += 2
|
||||
elif opt == "--timeout":
|
||||
i += 2
|
||||
elif opt == "--env":
|
||||
env_val = opts[i+1]
|
||||
if "=" in env_val:
|
||||
@@ -420,6 +429,16 @@ elif cmd1 == "agent":
|
||||
"cline": "Cline Chat Ready"
|
||||
}.get(agent_type, "Ready")
|
||||
|
||||
# Look up cwd from target pane in state if not explicitly passed
|
||||
if not cwd and pane:
|
||||
for p in state.get("panes", []):
|
||||
if p.get("pane_id") == pane:
|
||||
cwd = p.get("cwd", "")
|
||||
ws = p.get("workspace_id", ws)
|
||||
break
|
||||
if not ws and pane and ":" in pane:
|
||||
ws = pane.split(":")[0]
|
||||
|
||||
agents = state.get("agents", {})
|
||||
agents[name] = {
|
||||
"agent": agent_type,
|
||||
@@ -427,7 +446,7 @@ elif cmd1 == "agent":
|
||||
"cwd": cwd or "TMP_PATH_PLACEHOLDER",
|
||||
"workspace_id": ws or "w1",
|
||||
"pid": 9999,
|
||||
"pane_id": f"w1:p_{name}",
|
||||
"pane_id": pane or f"w1:p_{name}",
|
||||
"command": " ".join(agent_cmd),
|
||||
"buffer": buffer_content
|
||||
}
|
||||
@@ -528,8 +547,13 @@ elif cmd1 == "agent":
|
||||
sys.exit(1)
|
||||
name = args[2]
|
||||
agents = state.get("agents", {})
|
||||
if name in agents:
|
||||
agent_data = agents[name]
|
||||
matched_k = None
|
||||
for k in agents:
|
||||
if k == name or (len(k) > 32 and f"{k[:16]}-{k[-15:]}"[:32] == name) or (len(name) > 32 and f"{name[:16]}-{name[-15:]}"[:32] == k):
|
||||
matched_k = k
|
||||
break
|
||||
if matched_k:
|
||||
agent_data = agents[matched_k]
|
||||
pane_info = {
|
||||
"pid": agent_data.get("pid", 9999),
|
||||
"cwd": agent_data.get("cwd", ""),
|
||||
@@ -557,27 +581,55 @@ elif cmd1 == "agent":
|
||||
sys.exit(1)
|
||||
name = args[2]
|
||||
agents = state.get("agents", {})
|
||||
if name in agents:
|
||||
buffer_content = agents[name].get("buffer", "Ready")
|
||||
matched_k = None
|
||||
for k in agents:
|
||||
if k == name or (len(k) > 32 and f"{k[:16]}-{k[-15:]}"[:32] == name) or (len(name) > 32 and f"{name[:16]}-{name[-15:]}"[:32] == k):
|
||||
matched_k = k
|
||||
break
|
||||
if matched_k:
|
||||
buffer_content = agents[matched_k].get("buffer", "Ready")
|
||||
print(buffer_content)
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.stderr.write("Agent " + name + " not found\\\\n")
|
||||
sys.exit(1)
|
||||
elif cmd2 == "prompt":
|
||||
if len(args) < 4:
|
||||
sys.exit(1)
|
||||
name = args[2]
|
||||
text = args[3]
|
||||
agents = state.get("agents", {})
|
||||
matched_k = None
|
||||
for k in agents:
|
||||
if k == name or (len(k) > 32 and f"{k[:16]}-{k[-15:]}"[:32] == name) or (len(name) > 32 and f"{name[:16]}-{name[-15:]}"[:32] == k):
|
||||
matched_k = k
|
||||
break
|
||||
if matched_k:
|
||||
agents[matched_k]["sent_text"] = agents[matched_k].get("sent_text", "") + text
|
||||
agents[matched_k]["buffer"] = agents[matched_k].get("buffer", "") + "\\\\n" + text + "\\\\n\\\\nesc to interrupt"
|
||||
state["agents"] = agents
|
||||
save_state()
|
||||
print(json.dumps({"id": "cli:agent:prompt", "result": {"type": "ok"}}))
|
||||
sys.exit(0)
|
||||
elif cmd2 == "send":
|
||||
if len(args) < 4:
|
||||
sys.exit(1)
|
||||
name = args[2]
|
||||
text = args[3]
|
||||
agents = state.get("agents", {})
|
||||
if name in agents:
|
||||
agents[name]["sent_text"] = agents[name].get("sent_text", "") + text
|
||||
matched_k = None
|
||||
for k in agents:
|
||||
if k == name or (len(k) > 32 and f"{k[:16]}-{k[-15:]}"[:32] == name) or (len(name) > 32 and f"{name[:16]}-{name[-15:]}"[:32] == k):
|
||||
matched_k = k
|
||||
break
|
||||
if matched_k:
|
||||
agents[matched_k]["sent_text"] = agents[matched_k].get("sent_text", "") + text
|
||||
if text in ("C-m", "Enter"):
|
||||
agents[name]["buffer"] = agents[name].get("buffer", "") + "\\\\n\\\\nesc to interrupt"
|
||||
agents[matched_k]["buffer"] = agents[matched_k].get("buffer", "") + "\\\\n\\\\nesc to interrupt"
|
||||
else:
|
||||
agents[name]["buffer"] = agents[name].get("buffer", "") + "\\\\n" + text
|
||||
agents[matched_k]["buffer"] = agents[matched_k].get("buffer", "") + "\\\\n" + text
|
||||
if "/exit" in text or "exit" in text or "Exit" in text:
|
||||
agents[name]["status"] = "stopped"
|
||||
agents[matched_k]["status"] = "stopped"
|
||||
state["agents"] = agents
|
||||
save_state()
|
||||
sys.exit(0)
|
||||
@@ -671,8 +723,13 @@ elif cmd1 == "list-panes":
|
||||
if "-t" in args:
|
||||
session_target = args[args.index("-t") + 1]
|
||||
agents = state.get("agents", {})
|
||||
if session_target in agents:
|
||||
data = agents[session_target]
|
||||
matched_k = None
|
||||
for k in agents:
|
||||
if k == session_target or (len(k) > 32 and f"{k[:16]}-{k[-15:]}"[:32] == session_target) or (len(session_target) > 32 and f"{session_target[:16]}-{session_target[-15:]}"[:32] == k):
|
||||
matched_k = k
|
||||
break
|
||||
if matched_k:
|
||||
data = agents[matched_k]
|
||||
pid = data.get("pid", 9999)
|
||||
cwd = data.get("cwd", "TMP_PATH_PLACEHOLDER")
|
||||
cmd = data.get("command", "claude")
|
||||
@@ -696,7 +753,7 @@ elif cmd1 == "has-session":
|
||||
elif len(args) > 1:
|
||||
sess_target = args[1]
|
||||
agents = state.get("agents", {})
|
||||
if sess_target in agents:
|
||||
if any(k == sess_target or (len(k) > 32 and f"{k[:16]}-{k[-15:]}"[:32] == sess_target) or (len(sess_target) > 32 and f"{sess_target[:16]}-{sess_target[-15:]}"[:32] == k) for k in agents):
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
@@ -708,10 +765,11 @@ elif cmd1 == "kill-session":
|
||||
elif len(args) > 1:
|
||||
sess_target = args[1]
|
||||
agents = state.get("agents", {})
|
||||
if sess_target in agents:
|
||||
del agents[sess_target]
|
||||
state["agents"] = agents
|
||||
save_state()
|
||||
to_del = [k for k in agents if k == sess_target or (len(k) > 32 and f"{k[:16]}-{k[-15:]}"[:32] == sess_target) or (len(sess_target) > 32 and f"{sess_target[:16]}-{sess_target[-15:]}"[:32] == k)]
|
||||
for k in to_del:
|
||||
del agents[k]
|
||||
state["agents"] = agents
|
||||
save_state()
|
||||
sys.exit(0)
|
||||
|
||||
elif cmd1 == "ls":
|
||||
|
||||
@@ -15,7 +15,12 @@ _pane_quiescent() {{ return 0; }}
|
||||
_pane_dialog_open() {{ return 1; }}
|
||||
|
||||
# Mock _sks_herdr commands
|
||||
_sks_herdr() {{ return 0; }}
|
||||
_sks_herdr() {{
|
||||
if [ "${{1:-}}" = "agent" ] && [ "${{2:-}}" = "prompt" ]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}}
|
||||
|
||||
# Mock _pane_capture to simulate fixed pane content (no execution token, no pane change)
|
||||
_pane_capture() {{ echo "static content"; }}
|
||||
|
||||
@@ -39,7 +39,7 @@ 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"}
|
||||
whitelist = {"--cwd", "--workspace", "--tab", "--split", "--env", "--focus", "--no-focus", "--kind", "--pane", "--timeout"}
|
||||
for call in agent_start_calls:
|
||||
try:
|
||||
dd_idx = call.index("--")
|
||||
|
||||
@@ -67,6 +67,6 @@ def test_create_session_full(mam_sandbox, mock_herdr, mock_agents):
|
||||
|
||||
sessions = reg.get("herdr_sessions", [])
|
||||
assert len(sessions) == 1
|
||||
assert sessions[0]["name"] == session_name
|
||||
assert sessions[0]["name"].endswith("-creator-claude")
|
||||
assert sessions[0]["status"] == "running"
|
||||
assert sessions[0]["role"] == "Creator"
|
||||
|
||||
@@ -439,8 +439,8 @@ d['herdr_sessions'] = [{
|
||||
# Verify mock herdr calls recorded the keys "/exit" sent
|
||||
calls = state.get("calls", [])
|
||||
|
||||
# Should see send-keys call
|
||||
assert any("send" in call and "/exit" in call for call in calls)
|
||||
# Should see send-keys or agent prompt call
|
||||
assert any(("send" in call or "prompt" in call) and "/exit" in call for call in calls)
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
|
||||
Reference in New Issue
Block a user