fix(herdr): support Herdr 0.8.0 pane allocation, agent prompt, and length limits

This commit is contained in:
2026-08-14 10:49:29 +09:00
parent 720f8ad224
commit a7f3fc3242
9 changed files with 185 additions and 50 deletions
+77 -20
View File
@@ -188,6 +188,18 @@ _real_herdr() {
fi
}
_sanitize_herdr_agent_name() {
local n="$1"
if [ "${#n}" -le 32 ]; then
printf '%s\n' "$n"
return 0
fi
local p="${n:0:16}"
local s="${n: -15}"
local res="${p}-${s}"
printf '%s\n' "${res:0:32}"
}
wrapper_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
cmd="${1:-}"
if [ -z "$cmd" ]; then
@@ -196,7 +208,6 @@ if [ -z "$cmd" ]; then
fi
shift
case "$cmd" in
has-session)
sess=""
@@ -213,7 +224,7 @@ case "$cmd" in
*) shift ;;
esac
done
_real_herdr agent get "$sess" >/dev/null
_real_herdr agent get "$(_sanitize_herdr_agent_name "$sess")" >/dev/null 2>&1 || _real_herdr agent get "$sess" >/dev/null
;;
new-session)
name="" ws="" run_cmd=""
@@ -317,8 +328,7 @@ except Exception:
" 2>/dev/null || echo "")
ws_id=""
split_arg=""
target_pane=""
if [ -n "$existing_ws" ]; then
# W2a: Determine split direction policy via pane layout
sample_pane=$(_real_herdr pane list 2>/dev/null | TARGET_WS="$existing_ws" python3 -c "
@@ -367,26 +377,42 @@ except Exception:
fi
if [ "$split_dir" = "right" ] || [ "$split_dir" = "down" ]; then
ws_id="$existing_ws"
split_arg="--split $split_dir"
split_json=$(_real_herdr pane split --pane "$sample_pane" --direction "$split_dir" --cwd "${ws:-.}" --no-focus 2>/dev/null || echo "")
target_pane=$(echo "$split_json" | python3 -c "
import sys, json
try:
d = json.loads(sys.stdin.read())
res = d.get('result', {})
print(res.get('pane', {}).get('pane_id') or res.get('pane_id', ''))
except Exception:
pass
" 2>/dev/null || echo "")
elif [ "$split_dir" = "overflow" ]; then
# W2b: Overflow threshold reached — force create fresh workspace
existing_ws=""
else
ws_id="$existing_ws"
split_arg="--split right"
split_json=$(_real_herdr pane split --pane "$sample_pane" --direction right --cwd "${ws:-.}" --no-focus 2>/dev/null || echo "")
target_pane=$(echo "$split_json" | python3 -c "
import sys, json
try:
d = json.loads(sys.stdin.read())
res = d.get('result', {})
print(res.get('pane', {}).get('pane_id') or res.get('pane_id', ''))
except Exception:
pass
" 2>/dev/null || echo "")
fi
fi
if [ -z "$existing_ws" ]; then
if [ -z "$existing_ws" ] || [ -z "$target_pane" ]; then
ws_json=$(_real_herdr workspace create --cwd "${ws:-.}" --no-focus 2>/dev/null || echo "")
ws_id=$(echo "$ws_json" | python3 -c "
target_pane=$(echo "$ws_json" | python3 -c "
import sys, json
try:
d = json.loads(sys.stdin.read())
res = d.get('result', {})
w_obj = res.get('workspace', {})
print(w_obj.get('workspace_id') or res.get('workspace_id', ''))
print(res.get('root_pane', {}).get('pane_id') or w_obj.get('root_pane_id') or res.get('root_pane_id') or res.get('pane_id', ''))
except Exception:
pass
" 2>/dev/null || echo "")
@@ -396,11 +422,20 @@ except Exception:
res=""
success=0
backoffs=(0.5 1 2)
agent_name=$(_sanitize_herdr_agent_name "$name")
for i in $(seq 0 2); do
if [ -n "$ws_id" ]; then
res=$(eval "_real_herdr agent start \"$name\" --workspace \"$ws_id\" --cwd \"${ws:-.}\" $split_arg $env_flags -- $final_cmd" 2>&1 || true)
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
else
res=$(eval "_real_herdr agent start \"$name\" --cwd \"${ws:-.}\" $split_arg $env_flags -- $final_cmd" 2>&1 || true)
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
fi
if echo "$res" | grep -q "agent_started"; then
success=1
@@ -438,7 +473,14 @@ except Exception:
# `herdr session list`), not individual agent panes — `session stop/delete
# "$sess"` with a MAM session name always fails (silently, via `|| true`).
# Resolve the real pane_id via `agent get` and close just that pane instead.
pane_id=$(_real_herdr agent get "$sess" 2>/dev/null | python3 -c "
agent_target=$(_sanitize_herdr_agent_name "$sess")
pane_id=$(_real_herdr agent get "$agent_target" 2>/dev/null | python3 -c "
import sys, json
try:
print(json.load(sys.stdin).get('result', {}).get('agent', {}).get('pane_id', ''))
except Exception:
pass
" 2>/dev/null || _real_herdr agent get "$sess" 2>/dev/null | python3 -c "
import sys, json
try:
print(json.load(sys.stdin).get('result', {}).get('agent', {}).get('pane_id', ''))
@@ -448,7 +490,7 @@ except Exception:
if [ -n "$pane_id" ]; then
_real_herdr pane close "$pane_id" >/dev/null 2>&1 || true
fi
_real_herdr kill-session -t "$sess" >/dev/null 2>&1 || true
_real_herdr kill-session -t "$agent_target" >/dev/null 2>&1 || _real_herdr kill-session -t "$sess" >/dev/null 2>&1 || true
;;
list-panes)
sess="" format=""
@@ -477,7 +519,8 @@ except Exception:
# there is no top-level "pane" key, and no "pid" field at all (only
# "pane_id", herdr's own wN:pN identifier). The real OS pid requires a
# second call to `pane process-info --pane <pane_id>`.
info=$(_real_herdr agent get "$sess" 2>/dev/null || true)
agent_target=$(_sanitize_herdr_agent_name "$sess")
info=$(_real_herdr agent get "$agent_target" 2>/dev/null || _real_herdr agent get "$sess" 2>/dev/null || true)
pane_id="" cwd="" cmd=""
if [ -n "$info" ]; then
parsed=$(python3 -c "
@@ -546,7 +589,8 @@ except Exception:
*) shift ;;
esac
done
_real_herdr agent read "$sess" --source visible --lines 100 2>/dev/null || true
agent_target=$(_sanitize_herdr_agent_name "$sess")
_real_herdr agent read "$agent_target" --source visible --lines 100 2>/dev/null || _real_herdr agent read "$sess" --source visible --lines 100 2>/dev/null || true
;;
send-keys)
sess="" key=""
@@ -568,7 +612,14 @@ except Exception:
fi
# `pane send-keys` requires a real pane_id ("wN:pN"), not an agent name —
# resolve it via `agent get` first (agent-level commands accept names).
pane_id=$(_real_herdr agent get "$sess" 2>/dev/null | python3 -c "
agent_target=$(_sanitize_herdr_agent_name "$sess")
pane_id=$(_real_herdr agent get "$agent_target" 2>/dev/null | python3 -c "
import sys, json
try:
print(json.load(sys.stdin).get('result', {}).get('agent', {}).get('pane_id', ''))
except Exception:
pass
" 2>/dev/null || _real_herdr agent get "$sess" 2>/dev/null | python3 -c "
import sys, json
try:
print(json.load(sys.stdin).get('result', {}).get('agent', {}).get('pane_id', ''))
@@ -578,7 +629,7 @@ except Exception:
if [ -n "$pane_id" ]; then
_real_herdr pane send-keys "$pane_id" "$key" >/dev/null 2>&1 || true
else
_real_herdr pane send-keys "$sess" "$key" >/dev/null 2>&1 || true
_real_herdr pane send-keys "$agent_target" "$key" >/dev/null 2>&1 || _real_herdr pane send-keys "$sess" "$key" >/dev/null 2>&1 || true
fi
;;
set-buffer)
@@ -1551,6 +1602,12 @@ _pane_dialog_open() {
# changed); retry up to 3 times.
send_keys_safe() {
local sess="$1" text="$2" job_id="${3:-adhoc}"
# Native herdr 0.8+ fast path: agent prompt handles atomic text + enter submission
if _sks_herdr agent prompt "$sess" "$text" >/dev/null 2>&1; then
return 0
fi
local marker pre_submit deadline try
# Verification token: last 24 *characters* (not bytes — `tail -c` can split a
# multi-byte UTF-8 char, e.g. Korean, producing a marker that can never match