fix(herdr): support Herdr 0.8.0 pane allocation, agent prompt, and length limits
This commit is contained in:
+77
-20
@@ -188,6 +188,18 @@ _real_herdr() {
|
|||||||
fi
|
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)
|
wrapper_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
cmd="${1:-}"
|
cmd="${1:-}"
|
||||||
if [ -z "$cmd" ]; then
|
if [ -z "$cmd" ]; then
|
||||||
@@ -196,7 +208,6 @@ if [ -z "$cmd" ]; then
|
|||||||
fi
|
fi
|
||||||
shift
|
shift
|
||||||
|
|
||||||
|
|
||||||
case "$cmd" in
|
case "$cmd" in
|
||||||
has-session)
|
has-session)
|
||||||
sess=""
|
sess=""
|
||||||
@@ -213,7 +224,7 @@ case "$cmd" in
|
|||||||
*) shift ;;
|
*) shift ;;
|
||||||
esac
|
esac
|
||||||
done
|
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)
|
new-session)
|
||||||
name="" ws="" run_cmd=""
|
name="" ws="" run_cmd=""
|
||||||
@@ -317,8 +328,7 @@ except Exception:
|
|||||||
" 2>/dev/null || echo "")
|
" 2>/dev/null || echo "")
|
||||||
|
|
||||||
ws_id=""
|
ws_id=""
|
||||||
split_arg=""
|
target_pane=""
|
||||||
|
|
||||||
if [ -n "$existing_ws" ]; then
|
if [ -n "$existing_ws" ]; then
|
||||||
# W2a: Determine split direction policy via pane layout
|
# W2a: Determine split direction policy via pane layout
|
||||||
sample_pane=$(_real_herdr pane list 2>/dev/null | TARGET_WS="$existing_ws" python3 -c "
|
sample_pane=$(_real_herdr pane list 2>/dev/null | TARGET_WS="$existing_ws" python3 -c "
|
||||||
@@ -367,26 +377,42 @@ except Exception:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$split_dir" = "right" ] || [ "$split_dir" = "down" ]; then
|
if [ "$split_dir" = "right" ] || [ "$split_dir" = "down" ]; then
|
||||||
ws_id="$existing_ws"
|
split_json=$(_real_herdr pane split --pane "$sample_pane" --direction "$split_dir" --cwd "${ws:-.}" --no-focus 2>/dev/null || echo "")
|
||||||
split_arg="--split $split_dir"
|
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
|
elif [ "$split_dir" = "overflow" ]; then
|
||||||
# W2b: Overflow threshold reached — force create fresh workspace
|
# W2b: Overflow threshold reached — force create fresh workspace
|
||||||
existing_ws=""
|
existing_ws=""
|
||||||
else
|
else
|
||||||
ws_id="$existing_ws"
|
split_json=$(_real_herdr pane split --pane "$sample_pane" --direction right --cwd "${ws:-.}" --no-focus 2>/dev/null || echo "")
|
||||||
split_arg="--split right"
|
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
|
||||||
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_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
|
import sys, json
|
||||||
try:
|
try:
|
||||||
d = json.loads(sys.stdin.read())
|
d = json.loads(sys.stdin.read())
|
||||||
res = d.get('result', {})
|
res = d.get('result', {})
|
||||||
w_obj = res.get('workspace', {})
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
" 2>/dev/null || echo "")
|
" 2>/dev/null || echo "")
|
||||||
@@ -396,11 +422,20 @@ except Exception:
|
|||||||
res=""
|
res=""
|
||||||
success=0
|
success=0
|
||||||
backoffs=(0.5 1 2)
|
backoffs=(0.5 1 2)
|
||||||
|
agent_name=$(_sanitize_herdr_agent_name "$name")
|
||||||
for i in $(seq 0 2); do
|
for i in $(seq 0 2); do
|
||||||
if [ -n "$ws_id" ]; then
|
if [ -n "$target_pane" ]; then
|
||||||
res=$(eval "_real_herdr agent start \"$name\" --workspace \"$ws_id\" --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}\" --pane \"$target_pane\" $env_flags -- $final_cmd" 2>&1 || true)
|
||||||
else
|
else
|
||||||
res=$(eval "_real_herdr agent start \"$name\" --cwd \"${ws:-.}\" $split_arg $env_flags -- $final_cmd" 2>&1 || true)
|
res=$(eval "_real_herdr agent start \"$agent_name\" --kind \"${kind:-claude}\" --pane \"$target_pane\" $env_flags" 2>&1 || true)
|
||||||
|
fi
|
||||||
|
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
|
||||||
fi
|
fi
|
||||||
if echo "$res" | grep -q "agent_started"; then
|
if echo "$res" | grep -q "agent_started"; then
|
||||||
success=1
|
success=1
|
||||||
@@ -438,7 +473,14 @@ except Exception:
|
|||||||
# `herdr session list`), not individual agent panes — `session stop/delete
|
# `herdr session list`), not individual agent panes — `session stop/delete
|
||||||
# "$sess"` with a MAM session name always fails (silently, via `|| true`).
|
# "$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.
|
# 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
|
import sys, json
|
||||||
try:
|
try:
|
||||||
print(json.load(sys.stdin).get('result', {}).get('agent', {}).get('pane_id', ''))
|
print(json.load(sys.stdin).get('result', {}).get('agent', {}).get('pane_id', ''))
|
||||||
@@ -448,7 +490,7 @@ except Exception:
|
|||||||
if [ -n "$pane_id" ]; then
|
if [ -n "$pane_id" ]; then
|
||||||
_real_herdr pane close "$pane_id" >/dev/null 2>&1 || true
|
_real_herdr pane close "$pane_id" >/dev/null 2>&1 || true
|
||||||
fi
|
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)
|
list-panes)
|
||||||
sess="" format=""
|
sess="" format=""
|
||||||
@@ -477,7 +519,8 @@ except Exception:
|
|||||||
# there is no top-level "pane" key, and no "pid" field at all (only
|
# 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
|
# "pane_id", herdr's own wN:pN identifier). The real OS pid requires a
|
||||||
# second call to `pane process-info --pane <pane_id>`.
|
# 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=""
|
pane_id="" cwd="" cmd=""
|
||||||
if [ -n "$info" ]; then
|
if [ -n "$info" ]; then
|
||||||
parsed=$(python3 -c "
|
parsed=$(python3 -c "
|
||||||
@@ -546,7 +589,8 @@ except Exception:
|
|||||||
*) shift ;;
|
*) shift ;;
|
||||||
esac
|
esac
|
||||||
done
|
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)
|
send-keys)
|
||||||
sess="" key=""
|
sess="" key=""
|
||||||
@@ -568,7 +612,14 @@ except Exception:
|
|||||||
fi
|
fi
|
||||||
# `pane send-keys` requires a real pane_id ("wN:pN"), not an agent name —
|
# `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).
|
# 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
|
import sys, json
|
||||||
try:
|
try:
|
||||||
print(json.load(sys.stdin).get('result', {}).get('agent', {}).get('pane_id', ''))
|
print(json.load(sys.stdin).get('result', {}).get('agent', {}).get('pane_id', ''))
|
||||||
@@ -578,7 +629,7 @@ except Exception:
|
|||||||
if [ -n "$pane_id" ]; then
|
if [ -n "$pane_id" ]; then
|
||||||
_real_herdr pane send-keys "$pane_id" "$key" >/dev/null 2>&1 || true
|
_real_herdr pane send-keys "$pane_id" "$key" >/dev/null 2>&1 || true
|
||||||
else
|
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
|
fi
|
||||||
;;
|
;;
|
||||||
set-buffer)
|
set-buffer)
|
||||||
@@ -1551,6 +1602,12 @@ _pane_dialog_open() {
|
|||||||
# changed); retry up to 3 times.
|
# changed); retry up to 3 times.
|
||||||
send_keys_safe() {
|
send_keys_safe() {
|
||||||
local sess="$1" text="$2" job_id="${3:-adhoc}"
|
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
|
local marker pre_submit deadline try
|
||||||
# Verification token: last 24 *characters* (not bytes — `tail -c` can split a
|
# 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
|
# multi-byte UTF-8 char, e.g. Korean, producing a marker that can never match
|
||||||
|
|||||||
@@ -458,6 +458,11 @@ 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):
|
||||||
|
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')}
|
||||||
alive_set = {(t['name'], t.get('server', 'default')) for t in herdr_sessions}
|
alive_set = {(t['name'], t.get('server', 'default')) for t in herdr_sessions}
|
||||||
@@ -474,7 +479,7 @@ if herdr_confirmed:
|
|||||||
if s.get('status') in ('terminated', 'archived', 'stopped'):
|
if s.get('status') in ('terminated', 'archived', 'stopped'):
|
||||||
continue
|
continue
|
||||||
srv = s.get('herdr_session') or s.get('herdr_server') or s.get('herdr_workspace') or 'default'
|
srv = s.get('herdr_session') or s.get('herdr_server') or s.get('herdr_workspace') or 'default'
|
||||||
if (name, srv) not in alive_set:
|
if (name, srv) not in alive_set and (_sanitize(name), srv) not in alive_set:
|
||||||
s['status'] = 'terminated'
|
s['status'] = 'terminated'
|
||||||
s['terminated_at'] = now_iso
|
s['terminated_at'] = now_iso
|
||||||
s['terminated_at_epoch'] = int(datetime.now(timezone.utc).timestamp())
|
s['terminated_at_epoch'] = int(datetime.now(timezone.utc).timestamp())
|
||||||
@@ -488,7 +493,7 @@ if herdr_confirmed:
|
|||||||
if herdr_confirmed:
|
if herdr_confirmed:
|
||||||
for t in herdr_sessions:
|
for t in herdr_sessions:
|
||||||
name = t['name']
|
name = t['name']
|
||||||
if name in yaml_session_names:
|
if name in yaml_session_names or any(_sanitize(y) == name for y in yaml_session_names):
|
||||||
continue
|
continue
|
||||||
workspace_root = os.environ.get('WORKSPACE_ROOT')
|
workspace_root = os.environ.get('WORKSPACE_ROOT')
|
||||||
if not workspace_root:
|
if not workspace_root:
|
||||||
|
|||||||
@@ -122,6 +122,11 @@ def get_job_status(s):
|
|||||||
|
|
||||||
|
|
||||||
sessions_detail = []
|
sessions_detail = []
|
||||||
|
def _sanitize(n):
|
||||||
|
if len(n) <= 32:
|
||||||
|
return n
|
||||||
|
return f"{n[:16]}-{n[-15:]}"[:32]
|
||||||
|
|
||||||
for s in d.get('herdr_sessions', []):
|
for s in d.get('herdr_sessions', []):
|
||||||
name = s.get('name', '?')
|
name = s.get('name', '?')
|
||||||
server = s.get('herdr_session') or s.get('herdr_server') or s.get('herdr_workspace') or 'default'
|
server = s.get('herdr_session') or s.get('herdr_server') or s.get('herdr_workspace') or 'default'
|
||||||
@@ -134,7 +139,7 @@ for s in d.get('herdr_sessions', []):
|
|||||||
'name': name,
|
'name': name,
|
||||||
'server': server,
|
'server': server,
|
||||||
'status': s.get('status', '?'),
|
'status': s.get('status', '?'),
|
||||||
'herdr_alive': f"{name}|{server}" in alive,
|
'herdr_alive': f"{name}|{server}" in alive or f"{_sanitize(name)}|{server}" in alive,
|
||||||
'cmd': pane.get('cmd'),
|
'cmd': pane.get('cmd'),
|
||||||
'role': s.get('role'),
|
'role': s.get('role'),
|
||||||
'resume_state': resume_on_disk(s),
|
'resume_state': resume_on_disk(s),
|
||||||
@@ -219,6 +224,11 @@ def get_job_status(s):
|
|||||||
return (jid, 'unknown')
|
return (jid, 'unknown')
|
||||||
|
|
||||||
|
|
||||||
|
def _sanitize(n):
|
||||||
|
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']})")
|
||||||
print("=" * 136)
|
print("=" * 136)
|
||||||
@@ -230,7 +240,7 @@ for s in sessions:
|
|||||||
name = s.get('name', '?')
|
name = s.get('name', '?')
|
||||||
server = s.get('herdr_session') or s.get('herdr_server') or s.get('herdr_workspace') or 'default'
|
server = s.get('herdr_session') or s.get('herdr_server') or s.get('herdr_workspace') or 'default'
|
||||||
status = s.get('status', '?')
|
status = s.get('status', '?')
|
||||||
herdr = 'alive' if f"{name}|{server}" in alive else 'dead'
|
herdr = 'alive' if (f"{name}|{server}" in alive or f"{_sanitize(name)}|{server}" in alive) else 'dead'
|
||||||
cmd = (s.get('pane') or {}).get('cmd', '?')
|
cmd = (s.get('pane') or {}).get('cmd', '?')
|
||||||
res = resume_on_disk(s)
|
res = resume_on_disk(s)
|
||||||
jid, jstatus = get_job_status(s)
|
jid, jstatus = get_job_status(s)
|
||||||
|
|||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
# 🛠️ Multi-Agent Mux 종합 개선 및 미해결 과제 백로그 (`IMPROVEMENTS.md`)
|
# 🛠️ Multi-Agent Mux 종합 개선 및 미해결 과제 백로그 (`IMPROVEMENTS.md`)
|
||||||
|
|
||||||
- **최종 갱신일**: 2026-08-14 (A-4 M0~M1 및 B-8 send_keys_safe agy 우회 제거 조치 완료 반영)
|
- **최종 갱신일**: 2026-08-14 (Herdr 0.8.0 호환성 패치 반영: Pane 선제할당, agent prompt fast-path, 32자 이름 매핑, status/reconcile 동기화)
|
||||||
- **통합 관리 대상**: 기존 `CODEBASE_REVIEW_REPORT.md` + `OPTIMIZATION.md`
|
- **통합 관리 대상**: 기존 `CODEBASE_REVIEW_REPORT.md` + `OPTIMIZATION.md`
|
||||||
- **총 추적 미해결 과제**: **10건** (아키텍처 2건, 엣지케이스 5건, 오케스트레이션 0건, 레거시 잔재 3건)
|
- **총 추적 미해결 과제**: **10건** (아키텍처 2건, 엣지케이스 5건, 오케스트레이션 0건, 레거시 잔재 3건)
|
||||||
- **완료된 과제**: **13건** (A-1, A-3, A-5, B-1, B-3, B-4, B-7, C-1, C-2, O-1, O-2, O-3, O-4-OrcOnboard)
|
- **완료된 과제**: **14건** (A-1, A-3, A-5, B-1, B-3, B-4, B-7, B-8, C-1, C-2, O-1, O-2, O-3, O-4-OrcOnboard, Herdr-0.8.0-Compat)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+75
-17
@@ -350,6 +350,7 @@ elif cmd1 == "agent":
|
|||||||
name = args[2]
|
name = args[2]
|
||||||
ws = ""
|
ws = ""
|
||||||
cwd = ""
|
cwd = ""
|
||||||
|
pane = ""
|
||||||
# Find where -- is
|
# Find where -- is
|
||||||
try:
|
try:
|
||||||
double_dash_idx = args.index("--")
|
double_dash_idx = args.index("--")
|
||||||
@@ -359,8 +360,8 @@ elif cmd1 == "agent":
|
|||||||
agent_cmd = []
|
agent_cmd = []
|
||||||
opts = args[3:]
|
opts = args[3:]
|
||||||
|
|
||||||
# W8: Whitelist herdr 0.7.4 allowed flags
|
# Whitelist herdr allowed flags (0.7.4 + 0.8.0)
|
||||||
whitelist = {"--cwd", "--workspace", "--tab", "--split", "--env", "--focus", "--no-focus"}
|
whitelist = {"--cwd", "--workspace", "--tab", "--split", "--env", "--focus", "--no-focus", "--kind", "--pane", "--timeout"}
|
||||||
i = 0
|
i = 0
|
||||||
unknown_flags = []
|
unknown_flags = []
|
||||||
while i < len(opts):
|
while i < len(opts):
|
||||||
@@ -375,6 +376,14 @@ elif cmd1 == "agent":
|
|||||||
elif opt == "--cwd":
|
elif opt == "--cwd":
|
||||||
cwd = opts[i+1]
|
cwd = opts[i+1]
|
||||||
i += 2
|
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":
|
elif opt == "--env":
|
||||||
env_val = opts[i+1]
|
env_val = opts[i+1]
|
||||||
if "=" in env_val:
|
if "=" in env_val:
|
||||||
@@ -420,6 +429,16 @@ elif cmd1 == "agent":
|
|||||||
"cline": "Cline Chat Ready"
|
"cline": "Cline Chat Ready"
|
||||||
}.get(agent_type, "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 = state.get("agents", {})
|
||||||
agents[name] = {
|
agents[name] = {
|
||||||
"agent": agent_type,
|
"agent": agent_type,
|
||||||
@@ -427,7 +446,7 @@ elif cmd1 == "agent":
|
|||||||
"cwd": cwd or "TMP_PATH_PLACEHOLDER",
|
"cwd": cwd or "TMP_PATH_PLACEHOLDER",
|
||||||
"workspace_id": ws or "w1",
|
"workspace_id": ws or "w1",
|
||||||
"pid": 9999,
|
"pid": 9999,
|
||||||
"pane_id": f"w1:p_{name}",
|
"pane_id": pane or f"w1:p_{name}",
|
||||||
"command": " ".join(agent_cmd),
|
"command": " ".join(agent_cmd),
|
||||||
"buffer": buffer_content
|
"buffer": buffer_content
|
||||||
}
|
}
|
||||||
@@ -528,8 +547,13 @@ elif cmd1 == "agent":
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
name = args[2]
|
name = args[2]
|
||||||
agents = state.get("agents", {})
|
agents = state.get("agents", {})
|
||||||
if name in agents:
|
matched_k = None
|
||||||
agent_data = agents[name]
|
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 = {
|
pane_info = {
|
||||||
"pid": agent_data.get("pid", 9999),
|
"pid": agent_data.get("pid", 9999),
|
||||||
"cwd": agent_data.get("cwd", ""),
|
"cwd": agent_data.get("cwd", ""),
|
||||||
@@ -557,27 +581,55 @@ elif cmd1 == "agent":
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
name = args[2]
|
name = args[2]
|
||||||
agents = state.get("agents", {})
|
agents = state.get("agents", {})
|
||||||
if name in agents:
|
matched_k = None
|
||||||
buffer_content = agents[name].get("buffer", "Ready")
|
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)
|
print(buffer_content)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
else:
|
else:
|
||||||
sys.stderr.write("Agent " + name + " not found\\\\n")
|
sys.stderr.write("Agent " + name + " not found\\\\n")
|
||||||
sys.exit(1)
|
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":
|
elif cmd2 == "send":
|
||||||
if len(args) < 4:
|
if len(args) < 4:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
name = args[2]
|
name = args[2]
|
||||||
text = args[3]
|
text = args[3]
|
||||||
agents = state.get("agents", {})
|
agents = state.get("agents", {})
|
||||||
if name in agents:
|
matched_k = None
|
||||||
agents[name]["sent_text"] = agents[name].get("sent_text", "") + text
|
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"):
|
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:
|
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:
|
if "/exit" in text or "exit" in text or "Exit" in text:
|
||||||
agents[name]["status"] = "stopped"
|
agents[matched_k]["status"] = "stopped"
|
||||||
state["agents"] = agents
|
state["agents"] = agents
|
||||||
save_state()
|
save_state()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@@ -671,8 +723,13 @@ elif cmd1 == "list-panes":
|
|||||||
if "-t" in args:
|
if "-t" in args:
|
||||||
session_target = args[args.index("-t") + 1]
|
session_target = args[args.index("-t") + 1]
|
||||||
agents = state.get("agents", {})
|
agents = state.get("agents", {})
|
||||||
if session_target in agents:
|
matched_k = None
|
||||||
data = agents[session_target]
|
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)
|
pid = data.get("pid", 9999)
|
||||||
cwd = data.get("cwd", "TMP_PATH_PLACEHOLDER")
|
cwd = data.get("cwd", "TMP_PATH_PLACEHOLDER")
|
||||||
cmd = data.get("command", "claude")
|
cmd = data.get("command", "claude")
|
||||||
@@ -696,7 +753,7 @@ elif cmd1 == "has-session":
|
|||||||
elif len(args) > 1:
|
elif len(args) > 1:
|
||||||
sess_target = args[1]
|
sess_target = args[1]
|
||||||
agents = state.get("agents", {})
|
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)
|
sys.exit(0)
|
||||||
else:
|
else:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -708,8 +765,9 @@ elif cmd1 == "kill-session":
|
|||||||
elif len(args) > 1:
|
elif len(args) > 1:
|
||||||
sess_target = args[1]
|
sess_target = args[1]
|
||||||
agents = state.get("agents", {})
|
agents = state.get("agents", {})
|
||||||
if sess_target in agents:
|
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)]
|
||||||
del agents[sess_target]
|
for k in to_del:
|
||||||
|
del agents[k]
|
||||||
state["agents"] = agents
|
state["agents"] = agents
|
||||||
save_state()
|
save_state()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|||||||
@@ -15,7 +15,12 @@ _pane_quiescent() {{ return 0; }}
|
|||||||
_pane_dialog_open() {{ return 1; }}
|
_pane_dialog_open() {{ return 1; }}
|
||||||
|
|
||||||
# Mock _sks_herdr commands
|
# 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)
|
# Mock _pane_capture to simulate fixed pane content (no execution token, no pane change)
|
||||||
_pane_capture() {{ echo "static content"; }}
|
_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"]
|
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"}
|
whitelist = {"--cwd", "--workspace", "--tab", "--split", "--env", "--focus", "--no-focus", "--kind", "--pane", "--timeout"}
|
||||||
for call in agent_start_calls:
|
for call in agent_start_calls:
|
||||||
try:
|
try:
|
||||||
dd_idx = call.index("--")
|
dd_idx = call.index("--")
|
||||||
|
|||||||
@@ -67,6 +67,6 @@ def test_create_session_full(mam_sandbox, mock_herdr, mock_agents):
|
|||||||
|
|
||||||
sessions = reg.get("herdr_sessions", [])
|
sessions = reg.get("herdr_sessions", [])
|
||||||
assert len(sessions) == 1
|
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]["status"] == "running"
|
||||||
assert sessions[0]["role"] == "Creator"
|
assert sessions[0]["role"] == "Creator"
|
||||||
|
|||||||
@@ -439,8 +439,8 @@ d['herdr_sessions'] = [{
|
|||||||
# Verify mock herdr calls recorded the keys "/exit" sent
|
# Verify mock herdr calls recorded the keys "/exit" sent
|
||||||
calls = state.get("calls", [])
|
calls = state.get("calls", [])
|
||||||
|
|
||||||
# Should see send-keys call
|
# Should see send-keys or agent prompt call
|
||||||
assert any("send" in call and "/exit" in call for call in calls)
|
assert any(("send" in call or "prompt" in call) and "/exit" in call for call in calls)
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user