Migrate backend to herdr using a seamless translation shim wrapper
This commit is contained in:
+152
-52
@@ -44,75 +44,172 @@ _TMUX_SKILLS_BIN_PATTERN="${_TMUX_SKILLS_BIN_PATTERN:-/.agents/skills/.bin}"
|
|||||||
TMUX_SERVER_NAME="${TMUX_SERVER_NAME:-default}"
|
TMUX_SERVER_NAME="${TMUX_SERVER_NAME:-default}"
|
||||||
|
|
||||||
_resolve_real_tmux_path() {
|
_resolve_real_tmux_path() {
|
||||||
if [ -z "${_REAL_TMUX_PATH:-}" ] || [[ "$_REAL_TMUX_PATH" == *"${_TMUX_SHIM_DIR_PATTERN}"* ]] || [[ "$_REAL_TMUX_PATH" == *"${_TMUX_SKILLS_BIN_PATTERN}"* ]]; then
|
_REAL_TMUX_PATH="tmux"
|
||||||
local dir save_ifs="$IFS"
|
export _REAL_TMUX_PATH
|
||||||
_REAL_TMUX_PATH=""
|
|
||||||
IFS=:
|
|
||||||
for dir in $PATH; do
|
|
||||||
if [[ "$dir" != *"${_TMUX_SHIM_DIR_PATTERN}"* ]] && [[ "$dir" != *"${_TMUX_SKILLS_BIN_PATTERN}"* ]] && [ -x "$dir/tmux" ]; then
|
|
||||||
_REAL_TMUX_PATH="$dir/tmux"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
IFS="$save_ifs"
|
|
||||||
if [ -z "$_REAL_TMUX_PATH" ]; then
|
|
||||||
_REAL_TMUX_PATH="tmux"
|
|
||||||
fi
|
|
||||||
export _REAL_TMUX_PATH
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_init_tmux_isolation() {
|
_init_tmux_isolation() {
|
||||||
_resolve_real_tmux_path
|
local wrapper_dir="$WORKSPACE_ROOT/.mam/shim"
|
||||||
if [ -n "${TMUX_SERVER_NAME:-}" ] && [ "$TMUX_SERVER_NAME" != "default" ]; then
|
mkdir -p "$wrapper_dir"
|
||||||
local wrapper_dir="${TMPDIR:-/tmp}${_TMUX_SHIM_DIR_PATTERN}${TMUX_SERVER_NAME}"
|
cat <<'EOF' > "$wrapper_dir/tmux"
|
||||||
if [[ ":$PATH:" != *":$wrapper_dir:"* ]]; then
|
|
||||||
mkdir -p "$wrapper_dir"
|
|
||||||
cat <<EOF > "$wrapper_dir/tmux"
|
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
if [ -z "\${TMUX_SERVER_NAME:-}" ] || [ "\$TMUX_SERVER_NAME" = "default" ]; then
|
# Herdr-to-Tmux translation shim wrapper
|
||||||
exec "$_REAL_TMUX_PATH" "\$@"
|
set -euo pipefail
|
||||||
else
|
|
||||||
exec "$_REAL_TMUX_PATH" -L "\$TMUX_SERVER_NAME" "\$@"
|
wrapper_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
cmd="${1:-}"
|
||||||
|
if [ -z "$cmd" ]; then
|
||||||
|
echo "tmux shim: no command specified" >&2
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
EOF
|
shift
|
||||||
chmod +x "$wrapper_dir/tmux"
|
|
||||||
export PATH="$wrapper_dir:$PATH"
|
case "$cmd" in
|
||||||
fi
|
has-session)
|
||||||
else
|
sess=""
|
||||||
# 격리 비활성화 시 shim 자동 cleanup (PATH에서 제거)
|
while [ $# -gt 0 ]; do
|
||||||
local new_path="" dir save_ifs="$IFS"
|
case "$1" in
|
||||||
IFS=:
|
-t) sess="$2"; shift 2 ;;
|
||||||
for dir in $PATH; do
|
*) shift ;;
|
||||||
if [[ "$dir" != *"${_TMUX_SHIM_DIR_PATTERN}"* ]] && [[ "$dir" != *"${_TMUX_SKILLS_BIN_PATTERN}"* ]]; then
|
esac
|
||||||
if [ -z "$new_path" ]; then
|
|
||||||
new_path="$dir"
|
|
||||||
else
|
|
||||||
new_path="$new_path:$dir"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
IFS="$save_ifs"
|
herdr agent get "$sess" >/dev/null 2>&1
|
||||||
export PATH="$new_path"
|
;;
|
||||||
|
new-session)
|
||||||
|
name="" ws="" run_cmd=""
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-s) name="$2"; shift 2 ;;
|
||||||
|
-c) ws="$2"; shift 2 ;;
|
||||||
|
-d|-x|-y) shift ;;
|
||||||
|
*) run_cmd="$1"; shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
herdr workspace create --label "${TMUX_SERVER_NAME:-default}" --cwd "${ws:-.}" >/dev/null 2>&1 || true
|
||||||
|
# herdr agent start name workspace cwd argv
|
||||||
|
herdr agent start "$name" --workspace "${TMUX_SERVER_NAME:-default}" --cwd "${ws:-.}" -- $run_cmd
|
||||||
|
;;
|
||||||
|
kill-session)
|
||||||
|
sess=""
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-t) sess="$2"; shift 2 ;;
|
||||||
|
*) shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
herdr session stop "$sess" >/dev/null 2>&1 || true
|
||||||
|
herdr session delete "$sess" >/dev/null 2>&1 || true
|
||||||
|
;;
|
||||||
|
list-panes)
|
||||||
|
sess="" format=""
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-t) sess="$2"; shift 2 ;;
|
||||||
|
-F) format="$2"; shift 2 ;;
|
||||||
|
*) shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
info=$(herdr agent get "$sess" 2>/dev/null || true)
|
||||||
|
if [ -n "$info" ]; then
|
||||||
|
if [ "$format" = '#{pane_pid}|#{pane_current_path}|#{pane_current_command}' ]; then
|
||||||
|
python3 -c "
|
||||||
|
import sys, json
|
||||||
|
try:
|
||||||
|
d = json.loads(sys.stdin.read())
|
||||||
|
p = d.get('pane', d)
|
||||||
|
print(f\"{p.get('pid', '')}|{p.get('cwd', '')}|{p.get('command', p.get('argv', ''))}\")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
" <<< "$info"
|
||||||
|
elif [ "$format" = '#{pane_pid}' ]; then
|
||||||
|
python3 -c "import sys, json; d = json.loads(sys.stdin.read()); print(d.get('pane', d).get('pid', ''))" <<< "$info"
|
||||||
|
elif [ "$format" = '#{pane_current_path}' ]; then
|
||||||
|
python3 -c "import sys, json; d = json.loads(sys.stdin.read()); print(d.get('pane', d).get('cwd', ''))" <<< "$info"
|
||||||
|
elif [ "$format" = '#{pane_current_command}' ]; then
|
||||||
|
python3 -c "import sys, json; d = json.loads(sys.stdin.read()); print(d.get('pane', d).get('command', d.get('pane', d).get('argv', '')))" <<< "$info"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
capture-pane)
|
||||||
|
sess=""
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-t) sess="$2"; shift 2 ;;
|
||||||
|
*) shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
herdr agent read "$sess" --source visible --lines 100 2>/dev/null || true
|
||||||
|
;;
|
||||||
|
send-keys)
|
||||||
|
sess="" key=""
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-t) sess="$2"; shift 2 ;;
|
||||||
|
*) key="$1"; shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
if [ "$key" = "C-m" ] || [ "$key" = "Enter" ]; then
|
||||||
|
key="Enter"
|
||||||
|
fi
|
||||||
|
herdr pane send-keys "$sess" "$key" >/dev/null 2>&1 || true
|
||||||
|
;;
|
||||||
|
set-buffer)
|
||||||
|
text=""
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-b) buf="$2"; shift 2 ;;
|
||||||
|
*) text="$1"; shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
echo -n "$text" > "$wrapper_dir/tmp_buffer"
|
||||||
|
;;
|
||||||
|
paste-buffer)
|
||||||
|
sess=""
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-t) sess="$2"; shift 2 ;;
|
||||||
|
*) shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
if [ -f "$wrapper_dir/tmp_buffer" ]; then
|
||||||
|
herdr agent send "$sess" "$(cat "$wrapper_dir/tmp_buffer")" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
delete-buffer)
|
||||||
|
rm -f "$wrapper_dir/tmp_buffer"
|
||||||
|
;;
|
||||||
|
ls)
|
||||||
|
herdr session list --json 2>/dev/null | python3 -c "
|
||||||
|
import sys, json
|
||||||
|
try:
|
||||||
|
for s in json.load(sys.stdin):
|
||||||
|
print(f\"{s['name']}|{s.get('created_at_epoch', 0)}\")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
" || true
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
EOF
|
||||||
|
chmod +x "$wrapper_dir/tmux"
|
||||||
|
if [[ ":$PATH:" != *":$wrapper_dir:"* ]]; then
|
||||||
|
export PATH="$wrapper_dir:$PATH"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
mam_tmux() {
|
mam_tmux() {
|
||||||
_resolve_real_tmux_path
|
_init_tmux_isolation
|
||||||
if [ -n "${TMUX_SERVER_NAME:-}" ] && [ "$TMUX_SERVER_NAME" != "default" ]; then
|
"$WORKSPACE_ROOT/.mam/shim/tmux" "$@"
|
||||||
"$_REAL_TMUX_PATH" -L "$TMUX_SERVER_NAME" "$@"
|
|
||||||
else
|
|
||||||
"$_REAL_TMUX_PATH" "$@"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_tmux() {
|
_tmux() {
|
||||||
_init_tmux_isolation
|
|
||||||
mam_tmux "$@"
|
mam_tmux "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
tmux() {
|
tmux() {
|
||||||
_tmux "$@"
|
mam_tmux "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -1239,3 +1336,6 @@ handle_startup_dialogs() {
|
|||||||
done
|
done
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Auto-initialize the herdr translation shim wrapper on library source
|
||||||
|
_init_tmux_isolation
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ fi
|
|||||||
[ -n "$AGENT" ] || { echo "ERROR: --agent required" >&2; usage; exit 2; }
|
[ -n "$AGENT" ] || { echo "ERROR: --agent required" >&2; usage; exit 2; }
|
||||||
[ -n "$ROLE" ] || { echo "ERROR: --role required" >&2; usage; exit 2; }
|
[ -n "$ROLE" ] || { echo "ERROR: --role required" >&2; usage; exit 2; }
|
||||||
[ -d "$WORKSPACE" ] || { echo "ERROR: workspace $WORKSPACE not a directory" >&2; exit 1; }
|
[ -d "$WORKSPACE" ] || { echo "ERROR: workspace $WORKSPACE not a directory" >&2; exit 1; }
|
||||||
command -v tmux >/dev/null || { echo "ERROR: tmux not installed" >&2; exit 1; }
|
command -v herdr >/dev/null || { echo "ERROR: herdr not installed" >&2; exit 1; }
|
||||||
command -v "$AGENT" >/dev/null || { echo "ERROR: $AGENT CLI not in PATH" >&2; exit 1; }
|
command -v "$AGENT" >/dev/null || { echo "ERROR: $AGENT CLI not in PATH" >&2; exit 1; }
|
||||||
|
|
||||||
# Auth Check (OAuth check for agy, loggedIn check for claude, status for hermes)
|
# Auth Check (OAuth check for agy, loggedIn check for claude, status for hermes)
|
||||||
@@ -231,23 +231,7 @@ TMUX_EPOCH=$(date +%s)
|
|||||||
NOW_ISO=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
NOW_ISO=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||||
|
|
||||||
# 시작 명령 (CMD_FULL 은 spawn 전에 isolation 디스패치를 반영해 확정됨 — T4)
|
# 시작 명령 (CMD_FULL 은 spawn 전에 isolation 디스패치를 반영해 확정됨 — T4)
|
||||||
local_tmux="tmux"
|
START_CMD="herdr agent start \"$SESSION_NAME\" --workspace \"${TMUX_SERVER_NAME:-default}\" --cwd \"$WORKSPACE\" -- $CMD_FULL"
|
||||||
if [ -n "${TMUX_SERVER_NAME:-}" ] && [ "$TMUX_SERVER_NAME" != "default" ]; then
|
|
||||||
local_tmux="tmux -L $TMUX_SERVER_NAME"
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$AGENT" in
|
|
||||||
claude)
|
|
||||||
if [ "$ISOLATE" != "1" ] && [ -x "$WRAPPER" ]; then
|
|
||||||
START_CMD="$WRAPPER # ~/.local/bin 의 래퍼"
|
|
||||||
else
|
|
||||||
START_CMD="$local_tmux new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\""
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
agy|hermes|cline)
|
|
||||||
START_CMD="$local_tmux new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\""
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# If --onboard is specified, automatically build the onboarding prompt
|
# If --onboard is specified, automatically build the onboarding prompt
|
||||||
if [ "$ONBOARD" = "1" ] && [ -z "$SUBMIT_JOB_PROMPT" ]; then
|
if [ "$ONBOARD" = "1" ] && [ -z "$SUBMIT_JOB_PROMPT" ]; then
|
||||||
@@ -332,8 +316,8 @@ entry = {
|
|||||||
'cwd': os.environ['PANE_CWD'],
|
'cwd': os.environ['PANE_CWD'],
|
||||||
},
|
},
|
||||||
'start_command': os.environ['START_CMD'],
|
'start_command': os.environ['START_CMD'],
|
||||||
'attach_command': f'tmux {server_opt}attach -t {name}',
|
'attach_command': f'herdr session attach {name}',
|
||||||
'kill_command': f'tmux {server_opt}kill-session -t {name}',
|
'kill_command': f'herdr session stop {name} && herdr session delete {name}',
|
||||||
}
|
}
|
||||||
|
|
||||||
# T5: isolation 블록 영속화 (all-L2) — resume/resolve/stop 이 재적용의 단일 소스로 사용
|
# T5: isolation 블록 영속화 (all-L2) — resume/resolve/stop 이 재적용의 단일 소스로 사용
|
||||||
@@ -421,10 +405,6 @@ fi
|
|||||||
trap - EXIT
|
trap - EXIT
|
||||||
echo "agent-sessions.yaml updated"
|
echo "agent-sessions.yaml updated"
|
||||||
echo
|
echo
|
||||||
if [ -n "${TMUX_SERVER_NAME:-}" ] && [ "$TMUX_SERVER_NAME" != "default" ]; then
|
echo "Attach: herdr session attach $SESSION_NAME"
|
||||||
echo "Attach: tmux -L $TMUX_SERVER_NAME attach -t $SESSION_NAME"
|
|
||||||
else
|
|
||||||
echo "Attach: tmux attach -t $SESSION_NAME"
|
|
||||||
fi
|
|
||||||
echo "Delete: use multi-agent-mux-stop skill"
|
echo "Delete: use multi-agent-mux-stop skill"
|
||||||
echo "Resume: use multi-agent-mux-resume skill (after first message creates a session id)"
|
echo "Resume: use multi-agent-mux-resume skill (after first message creates a session id)"
|
||||||
|
|||||||
@@ -120,22 +120,13 @@ if [ -n "$ISO_ARGS" ]; then
|
|||||||
CMD_FULL="$CMD_FULL $ISO_ARGS"
|
CMD_FULL="$CMD_FULL $ISO_ARGS"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 4. Spawn new tmux session + run agent with the saved id
|
# 4. Spawn new agent session via herdr
|
||||||
case "$AGENT" in
|
herdr workspace create --label "${TMUX_SERVER_NAME:-default}" --cwd "$WORKSPACE" >/dev/null 2>&1 || true
|
||||||
claude)
|
herdr agent start "$SESSION_NAME" --workspace "${TMUX_SERVER_NAME:-default}" --cwd "$WORKSPACE" -- $CMD_FULL
|
||||||
if [ -z "$ISO_ROOT" ] && [ -x "$HOME/.local/bin/canary-projects-multi-agent-mux-creator-claude" ]; then
|
if [ "$AGENT" = "claude" ]; then
|
||||||
START_CMD="tmux new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$HOME/.local/bin/canary-projects-multi-agent-mux-creator-claude\""
|
# auto-handle trust / bypass dialogs
|
||||||
else
|
handle_startup_dialogs "$SESSION_NAME" 20
|
||||||
START_CMD="tmux new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\""
|
fi
|
||||||
fi
|
|
||||||
eval "$START_CMD"
|
|
||||||
# auto-handle trust / bypass dialogs
|
|
||||||
handle_startup_dialogs "$SESSION_NAME" 20
|
|
||||||
;;
|
|
||||||
agy|hermes|cline)
|
|
||||||
eval "tmux new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\""
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Wait for TUI readiness or let it settle
|
# Wait for TUI readiness or let it settle
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|||||||
@@ -189,9 +189,9 @@ def get_job_status(s):
|
|||||||
|
|
||||||
|
|
||||||
sessions = d.get('tmux_sessions', [])
|
sessions = d.get('tmux_sessions', [])
|
||||||
print(f"agent-sessions status — {drift['timestamp']} (tmux_confirmed={drift['tmux_confirmed']})")
|
print(f"agent-sessions status — {drift['timestamp']} (herdr_confirmed={drift['tmux_confirmed']})")
|
||||||
print("=" * 136)
|
print("=" * 136)
|
||||||
print(f"{'NAME':<44} {'SERVER':<12} {'YAML':<10} {'TMUX':<6} {'CMD':<6} {'RESUME':<8} {'JOB_ID':<10} {'JOB_STATUS':<12} DRIFT")
|
print(f"{'NAME':<44} {'WORKSPACE':<12} {'YAML':<10} {'HERDR':<6} {'CMD':<6} {'RESUME':<8} {'JOB_ID':<10} {'JOB_STATUS':<12} DRIFT")
|
||||||
print("-" * 136)
|
print("-" * 136)
|
||||||
if not sessions:
|
if not sessions:
|
||||||
print("(no sessions registered)")
|
print("(no sessions registered)")
|
||||||
@@ -199,12 +199,12 @@ for s in sessions:
|
|||||||
name = s.get('name', '?')
|
name = s.get('name', '?')
|
||||||
server = s.get('tmux_server') or 'default'
|
server = s.get('tmux_server') or 'default'
|
||||||
status = s.get('status', '?')
|
status = s.get('status', '?')
|
||||||
tmux = 'alive' if f"{name}|{server}" in alive else 'dead'
|
herdr = 'alive' if f"{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)
|
||||||
drs = ','.join(drift_by_name.get(name, [])) or '-'
|
drs = ','.join(drift_by_name.get(name, [])) or '-'
|
||||||
print(f"{name:<44} {server:<12} {status:<10} {tmux:<6} {cmd:<6} {res:<8} {jid:<10} {jstatus:<12} {drs}")
|
print(f"{name:<44} {server:<12} {status:<10} {herdr:<6} {cmd:<6} {res:<8} {jid:<10} {jstatus:<12} {drs}")
|
||||||
# drifts not tied to a registered row (e.g. class B unregistered, class D cache)
|
# drifts not tied to a registered row (e.g. class B unregistered, class D cache)
|
||||||
known = {s.get('name') for s in sessions}
|
known = {s.get('name') for s in sessions}
|
||||||
extra = [dr for dr in drift.get('drifts', []) if dr['name'] not in known]
|
extra = [dr for dr in drift.get('drifts', []) if dr['name'] not in known]
|
||||||
@@ -213,5 +213,5 @@ if extra:
|
|||||||
for dr in extra:
|
for dr in extra:
|
||||||
print(f" [{dr['class']}] {dr['msg']}")
|
print(f" [{dr['class']}] {dr['msg']}")
|
||||||
print("=" * 136)
|
print("=" * 136)
|
||||||
print(f"alive tmux: {sorted(alive)}")
|
print(f"alive herdr: {sorted(alive)}")
|
||||||
PYEOF
|
PYEOF
|
||||||
|
|||||||
@@ -201,9 +201,9 @@ if [ "$PURGE" = "1" ] && [ "$TMUX_ALIVE" = "1" ]; then
|
|||||||
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
||||||
echo "ERROR: session '$SESSION_NAME' is still alive after the kill chain." >&2
|
echo "ERROR: session '$SESSION_NAME' is still alive after the kill chain." >&2
|
||||||
echo " Refusing registry removal — records preserved (no state was modified)." >&2
|
echo " Refusing registry removal — records preserved (no state was modified)." >&2
|
||||||
echo " Diagnose the stuck TUI (tmux attach -t '$SESSION_NAME'), then re-run" >&2
|
echo " Diagnose the stuck TUI (herdr session attach '$SESSION_NAME'), then re-run" >&2
|
||||||
echo " stop_session.sh --purge-conversation --yes (retry is safe/idempotent)." >&2
|
echo " stop_session.sh --purge-conversation --yes (retry is safe/idempotent)." >&2
|
||||||
delegate_publish_event "$DELEGATE_JOB_ID" error "purge aborted: tmux session still alive"
|
delegate_publish_event "$DELEGATE_JOB_ID" error "purge aborted: herdr session still alive"
|
||||||
exit 4
|
exit 4
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
+6
-6
@@ -8,7 +8,7 @@ MAM은 단일 워크스페이스 상에서 복수의 에이전트(Claude, Cline,
|
|||||||
|
|
||||||
## 1. ⚙️ 사전 요구사항
|
## 1. ⚙️ 사전 요구사항
|
||||||
MAM 스킬 및 스크립트들은 호스트 시스템의 다음 도구들에 의존합니다. 설치 전에 확인해 주세요.
|
MAM 스킬 및 스크립트들은 호스트 시스템의 다음 도구들에 의존합니다. 설치 전에 확인해 주세요.
|
||||||
* **tmux**: 에이전트를 백그라운드 격리 Pane에서 구동하기 위한 프로세스 컨테이너
|
* **herdr**: 에이전트를 백그라운드 격리 Pane/Workspace에서 구동 및 관제하기 위한 프로세스 컨테이너
|
||||||
* **python3**: 세션 레지스트리(YAML/SQLite DB) 파싱 및 유효성 검사 (내장 `sqlite3` 모듈 필수)
|
* **python3**: 세션 레지스트리(YAML/SQLite DB) 파싱 및 유효성 검사 (내장 `sqlite3` 모듈 필수)
|
||||||
* **uuidgen**: 격리 세션 생성 시 고유의 UUID 할당
|
* **uuidgen**: 격리 세션 생성 시 고유의 UUID 할당
|
||||||
* **rsync**: 인스톨러(`deploy/install_mam.sh`)가 `.agents/` 오케스트레이터 및 스킬 폴더를 타겟 프로젝트에 복제하는 데 사용 (설치 시 필요)
|
* **rsync**: 인스톨러(`deploy/install_mam.sh`)가 `.agents/` 오케스트레이터 및 스킬 폴더를 타겟 프로젝트에 복제하는 데 사용 (설치 시 필요)
|
||||||
@@ -54,14 +54,14 @@ $ bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh \
|
|||||||
--role developer \
|
--role developer \
|
||||||
--session my-project-dev-claude \
|
--session my-project-dev-claude \
|
||||||
--isolate \
|
--isolate \
|
||||||
--tmux-server multi-agent-mux
|
--herdr-workspace multi-agent-mux
|
||||||
```
|
```
|
||||||
* `--isolate` 옵션을 주면 `.mam/agent_homes/<uuid>/` 하위에 로그인 및 설정은 유지하되 대화 내역은 격리되는 홈이 형성됩니다.
|
* `--isolate` 옵션을 주면 `.mam/agent_homes/<uuid>/` 하위에 로그인 및 설정은 유지하되 대화 내역은 격리되는 홈이 형성됩니다.
|
||||||
|
|
||||||
### 2) 세션 접속 (Attach)
|
### 2) 세션 접속 (Attach)
|
||||||
백그라운드에서 구동된 에이전트 TUI 화면에 들어갑니다. (세션 생성 시 지정한 독립 격리 tmux 서버 소켓 `-L multi-agent-mux` 를 경유해 접속합니다.)
|
백그라운드에서 구동된 에이전트 TUI 화면에 들어갑니다.
|
||||||
```bash
|
```bash
|
||||||
$ tmux -L multi-agent-mux attach -t my-project-dev-claude
|
$ herdr session attach my-project-dev-claude
|
||||||
```
|
```
|
||||||
* **화면 탈출**: 대화 중 세션을 유지한 채 터미널로 돌아오려면 `Ctrl + B`를 누른 뒤 `D` 키를 차례로 입력합니다.
|
* **화면 탈출**: 대화 중 세션을 유지한 채 터미널로 돌아오려면 `Ctrl + B`를 누른 뒤 `D` 키를 차례로 입력합니다.
|
||||||
|
|
||||||
@@ -81,8 +81,8 @@ $ [ -n "$UUID" ] || { echo "[ERROR] 매칭되는 활성 세션 이력이 없습
|
|||||||
|
|
||||||
# 2단계: 세션 재기동 (이전 대화 컨텍스트 복원 기동)
|
# 2단계: 세션 재기동 (이전 대화 컨텍스트 복원 기동)
|
||||||
# (주의: 만약 create 시 격리(--isolate) 세션으로 생성했다면, CLAUDE_CONFIG_DIR 환경변수를 YAML에 기록된 isolation.root 경로로 지정하여 띄워야 합니다. 상세 격리 복원 커맨드는 .agents/skills/multi-agent-mux-resume/SKILL.md 문서를 필독해 주세요.)
|
# (주의: 만약 create 시 격리(--isolate) 세션으로 생성했다면, CLAUDE_CONFIG_DIR 환경변수를 YAML에 기록된 isolation.root 경로로 지정하여 띄워야 합니다. 상세 격리 복원 커맨드는 .agents/skills/multi-agent-mux-resume/SKILL.md 문서를 필독해 주세요.)
|
||||||
$ tmux -L multi-agent-mux new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" \
|
$ herdr run -d --name "$SESSION_NAME" --workspace "$WORKSPACE" -- \
|
||||||
"claude --dangerously-skip-permissions -r $UUID; echo 'CLAUDE EXIT'; bash"
|
"claude --dangerously-skip-permissions -r $UUID"
|
||||||
|
|
||||||
# 3단계: 레지스트리 세션 상태를 running 으로 동기화 갱신
|
# 3단계: 레지스트리 세션 상태를 running 으로 동기화 갱신
|
||||||
$ bash .agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh \
|
$ bash .agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh \
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ fi
|
|||||||
|
|
||||||
# 1. Dependency Checks
|
# 1. Dependency Checks
|
||||||
log_info "Verifying host dependencies..."
|
log_info "Verifying host dependencies..."
|
||||||
DEPS=(tmux python3 rsync uuidgen)
|
DEPS=(herdr python3 rsync uuidgen)
|
||||||
MISSING_DEPS=()
|
MISSING_DEPS=()
|
||||||
for dep in "${DEPS[@]}"; do
|
for dep in "${DEPS[@]}"; do
|
||||||
if ! command -v "$dep" &>/dev/null; then
|
if ! command -v "$dep" &>/dev/null; then
|
||||||
@@ -93,6 +93,10 @@ done
|
|||||||
|
|
||||||
if [ ${#MISSING_DEPS[@]} -ne 0 ]; then
|
if [ ${#MISSING_DEPS[@]} -ne 0 ]; then
|
||||||
log_error "Missing required dependencies: ${MISSING_DEPS[*]}"
|
log_error "Missing required dependencies: ${MISSING_DEPS[*]}"
|
||||||
|
if [[ " ${MISSING_DEPS[*]} " == *" herdr "* ]]; then
|
||||||
|
log_error "To install herdr, please refer to the official installation guide:"
|
||||||
|
log_error " curl -fsSL https://herdr.dev/install.sh | sh"
|
||||||
|
fi
|
||||||
log_error "Please install them before using MAM."
|
log_error "Please install them before using MAM."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user