diff --git a/.agents/skills/lib.sh b/.agents/skills/lib.sh index 142964e..94e71fb 100644 --- a/.agents/skills/lib.sh +++ b/.agents/skills/lib.sh @@ -127,7 +127,7 @@ except Exception: fi _real_herdr() { - local session="${HERDR_SERVER_NAME:-}" + local session="${HERDR_SESSION_NAME:-}" if [ "$session" = "default" ]; then session="" fi @@ -547,25 +547,44 @@ PYEOF } # Despite the name (kept for caller compatibility — resume/stop/update_yaml_resumed -# all do `HERDR_SERVER_NAME="$(resolve_herdr_workspace "$SESSION_NAME")"`), this +# all do `HERDR_SESSION_NAME="$(resolve_herdr_workspace "$SESSION_NAME")"`), this # returns the isolated herdr *session* name to use for this MAM session row, not # a workspace id. Real isolation is `--session ` (see `_MAM_SESSION` in the # generated wrapper) — a workspace label match provides no actual isolation # since agent/pane commands are server-global regardless of workspace. + resolve_herdr_session() { local session_name="$1" - MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$session_name" python3 -c " + local workspace="${2:-}" + MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$session_name" TARGET_WS="$workspace" python3 -c " import sys, os, json name = os.environ['SESSION_NAME'] +ws = os.environ.get('TARGET_WS', '').strip() d = json.loads(os.environ.get('MAM_STATE_JSON', '{}')) for s in d.get('herdr_sessions', []): if s.get('name') == name: - print(s.get('herdr_session') or 'default') + print(s.get('herdr_session') or s.get('herdr_server') or s.get('herdr_workspace') or 'default') sys.exit(0) fallback = os.environ.get('HERDR_SESSION_NAME', '') if not fallback or fallback == 'default': - pwd = os.path.abspath(os.getcwd()) - fallback = 'mam-' + os.path.basename(pwd).lower().replace('_', '-') + legacy = os.environ.get('HERDR_SERVER_NAME', '') + if legacy and legacy != 'default': + fallback = legacy +if not fallback or fallback == 'default': + if ws: + # derive_workspace_slug Equivalent in python + abs_ws = os.path.abspath(ws) + parent = os.path.basename(os.path.dirname(abs_ws)) or 'workspace' + work = os.path.basename(abs_ws) or 'root' + if parent in ('/', '.'): parent = 'workspace' + if work in ('/', '.'): work = 'root' + slug = f'{parent}-{work}'.lower().replace('_', '-') + import re + slug = re.sub(r'[^a-zA-Z0-9-]', '', slug).lstrip('-') + fallback = f'mam-{slug}' if slug else 'mam-ws' + else: + fallback = 'default' + print('WARN: resolve_herdr_session called without workspace parameter for unregistered session; falling back to default', file=sys.stderr) print(fallback or 'default') " } @@ -575,24 +594,10 @@ resolve_herdr_workspace() { } # --------------------------------------------------------------------------- -# derive_session_name -# -# THE single source of truth for the herdr session name. Rule: -# slug = the two trailing path components of the absolute workspace, -# '_' -> '-', lowercased, joined with '-' -# name = "-creator-" -# -# Workspace root 기준 상대 해석. 예: -# $WORKSPACE_ROOT/landing_page/refer_landing_page + claude -# -> landing-page-refer-landing-page-creator-claude -# -# Decision (REVIEW P0-A): the actual workspace basename (refer_landing_page) -# IS included. The hand-written historical entry that dropped it -# (lab-landing-page-creator-claude) was the bug, not the convention. -# Every script and SKILL.md must use exactly this rule. +# derive_workspace_slug # --------------------------------------------------------------------------- -derive_session_name() { - local workspace="$1" agent="$2" +derive_workspace_slug() { + local workspace="${1:-$PWD}" local abs parent work slug abs="$(cd "$workspace" 2>/dev/null && pwd)" || abs="$workspace" parent="$(basename "$(dirname "$abs")" 2>/dev/null || echo "")" @@ -609,7 +614,22 @@ derive_session_name() { if [ -z "$slug" ]; then slug="ws" fi - printf '%s-creator-%s' "$slug" "$agent" + printf 'mam-%s' "$slug" +} + +# --------------------------------------------------------------------------- +# derive_session_name +# --------------------------------------------------------------------------- +derive_session_name() { + local workspace="${1:-$PWD}" agent="${2:-}" + local base_slug + base_slug="$(derive_workspace_slug "$workspace")" + local slug="${base_slug#mam-}" + if [ -n "$agent" ]; then + printf '%s-creator-%s' "$slug" "$agent" + else + printf '%s-creator-' "$slug" + fi } # --------------------------------------------------------------------------- @@ -1494,10 +1514,7 @@ start_watchdog() { # Waits up to 15 seconds for the agent's TUI to render its welcome screen. wait_for_tui_ready() { local sess="$1" agent="$2" - local local_herdr="herdr" i - if [ -n "${HERDR_SERVER_NAME:-}" ] && [ "$HERDR_SERVER_NAME" != "default" ]; then - local_herdr="herdr -L $HERDR_SERVER_NAME" - fi + local i for i in {1..30}; do if _pane_dialog_open "$sess"; then diff --git a/.agents/skills/multi-agent-mux-create/scripts/create_session.sh b/.agents/skills/multi-agent-mux-create/scripts/create_session.sh index 151e464..d8bbc4a 100755 --- a/.agents/skills/multi-agent-mux-create/scripts/create_session.sh +++ b/.agents/skills/multi-agent-mux-create/scripts/create_session.sh @@ -73,7 +73,7 @@ while [ $# -gt 0 ]; do done if [ -n "$HERDR_SERVER_OPT" ]; then - export HERDR_SERVER_NAME="$HERDR_SERVER_OPT" + export HERDR_SESSION_NAME="$HERDR_SERVER_OPT" fi # Preflight @@ -127,9 +127,9 @@ fi LOCAL_BIN="${LOCAL_BIN:-$HOME/.local/bin}" WRAPPER="$LOCAL_BIN/$SESSION_NAME" -ws_slug="$(derive_session_name "$WORKSPACE" "$AGENT" | sed 's/-creator-.*//')" +ws_slug="$(derive_workspace_slug "$WORKSPACE")" if [ -z "${HERDR_SESSION_NAME:-}" ] || [ "$HERDR_SESSION_NAME" = "default" ]; then - export HERDR_SESSION_NAME="mam-$ws_slug" + export HERDR_SESSION_NAME="$ws_slug" fi # Resolve absolute path of the agent command to prevent herdr PATH inheritance issues (especially on macOS) @@ -151,8 +151,8 @@ case "$AGENT" in esac spawn() { - if [ -z "${HERDR_SERVER_NAME:-}" ] || [ "$HERDR_SERVER_NAME" = "default" ]; then - export HERDR_SERVER_NAME="mam-$ws_slug" + if [ -z "${HERDR_SESSION_NAME:-}" ] || [ "$HERDR_SESSION_NAME" = "default" ]; then + export HERDR_SESSION_NAME="$ws_slug" fi case "$AGENT" in claude) @@ -160,11 +160,11 @@ spawn() { nohup "$WRAPPER" >/dev/null 2>&1 & disown else - HERDR_SERVER_NAME="$HERDR_SERVER_NAME" _herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "$CMD_FULL" + HERDR_SESSION_NAME="$HERDR_SESSION_NAME" _herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "$CMD_FULL" fi ;; agy|hermes|cline) - HERDR_SERVER_NAME="$HERDR_SERVER_NAME" _herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "$CMD_FULL" + HERDR_SESSION_NAME="$HERDR_SESSION_NAME" _herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "$CMD_FULL" ;; *) echo "ERROR: --agent must be claude, agy, hermes or cline, got: $AGENT" >&2; exit 2 ;; esac @@ -187,8 +187,8 @@ cleanup_herdr_on_error() { } trap cleanup_herdr_on_error EXIT -RESOLVED_SERVER="$(resolve_herdr_workspace "$SESSION_NAME")" -export HERDR_SERVER_NAME="${HERDR_SERVER_NAME:-$RESOLVED_SERVER}" +RESOLVED_SERVER="$(resolve_herdr_workspace "$SESSION_NAME" "$WORKSPACE")" +export HERDR_SESSION_NAME="${HERDR_SESSION_NAME:-$RESOLVED_SERVER}" # TUI 준비 대기 if ! wait_for_tui_ready "$SESSION_NAME" "$AGENT"; then @@ -209,7 +209,7 @@ NOW_ISO=$(date -u +'%Y-%m-%dT%H:%M:%SZ') # 시작 명령 # NOTE: this must match what `spawn()` actually ran above — env-var-driven # herdr server shim (HERDR_SERVER_NAME picked up by the lib.sh shim). -START_CMD="HERDR_SERVER_NAME=${HERDR_SERVER_NAME:-default} herdr new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\"" +START_CMD="HERDR_SESSION_NAME=${HERDR_SESSION_NAME:-default} herdr new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\"" # If --onboard is specified, automatically build the onboarding prompt if [ "$ONBOARD" = "1" ] && [ -z "$SUBMIT_JOB_PROMPT" ]; then @@ -283,6 +283,7 @@ entry = { 'herdr_session_created_at': os.environ['NOW_ISO'], 'herdr_session_epoch': int(epoch) if epoch.isdigit() else 0, 'herdr_session': server_name, + 'herdr_server': server_name, 'delegate_job_id': os.environ.get('DELEGATE_JOB_ID', '') or None, 'pane': { 'index': 0, diff --git a/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job b/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job index 25775ae..b4307ea 100755 --- a/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job +++ b/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job @@ -455,7 +455,7 @@ run_agent() { # the caller having exported HERDR_SERVER_NAME by hand. This is what lets # delegation reach an agent living in an isolated herdr session (e.g. one # created with --herdr-server) instead of silently looking in "default". - export HERDR_SERVER_NAME="$(resolve_herdr_workspace "$sess")" + export HERDR_SESSION_NAME="$(resolve_herdr_workspace "$sess" "$WORKDIR")" if ! herdr has-session -t "$sess" 2>/dev/null; then echo "ERROR: 에이전트 세션 '$sess'이 존재하지 않습니다. 작업을 위임하기 전에 먼저 에이전트 세션을 기동해 주세요." >&2 @@ -523,9 +523,9 @@ else: # NOTE: `herdr session attach` operates on whole herdr *sessions* (server # instances), not an individual agent by its MAM name — `agent attach` is - # the real command for that. HERDR_SERVER_NAME is inlined so the printed + # the real command for that. HERDR_SESSION_NAME is inlined so the printed # command is copy-pasteable in a fresh shell that hasn't sourced lib.sh. - echo "작업이 세션 '$sess'에 전송되었습니다. (연결하려면: HERDR_SERVER_NAME=$HERDR_SERVER_NAME herdr agent attach $sess — lib.sh를 source한 셸에서 실행)" + echo "작업이 세션 '$sess'에 전송되었습니다. (연결하려면: HERDR_SESSION_NAME=$HERDR_SESSION_NAME herdr agent attach $sess — lib.sh를 source한 셸에서 실행)" trap - EXIT } diff --git a/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job.13243_22852.tmp b/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job.13243_22852.tmp new file mode 100755 index 0000000..b4307ea --- /dev/null +++ b/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job.13243_22852.tmp @@ -0,0 +1,597 @@ +#!/usr/bin/env bash +# multi-agent-mux-delegate-job — user-facing orchestrator for the multi-agent-mux-delegate-job skill. +# +# Subcommands: +# submit register a job, start the subscriber FIRST, then run the agent, +# then (optionally) run a validation script. +# status show one job record. +# list list all jobs. +# verify run a user-supplied --validate script against a job's artifacts. +# wait block until all running/pending jobs reach a terminal state. +# +# This is a reference wrapper: it shells out to the python scripts that live +# next to it. Copy it into your project and customise as needed. It never hard +# fails if `claude`/`codex`/`herdr` are missing — it prints what it would run. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Load local env file (.mam.env preferred, .env fallback) from workspace root or explicit MAM_ENV_FILE +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" +TARGET_ENV="${MAM_ENV_FILE:-}" +if [[ -z "$TARGET_ENV" ]]; then + if [[ -f "$REPO_ROOT/.mam.env" ]]; then + TARGET_ENV="$REPO_ROOT/.mam.env" + elif [[ -f "$REPO_ROOT/.env" ]]; then + TARGET_ENV="$REPO_ROOT/.env" + echo "WARNING: Loading deprecated config file '$TARGET_ENV'. Please migrate to '.mam.env'." >&2 + elif [[ -f .mam.env ]]; then + TARGET_ENV=".mam.env" + echo "WARNING: Loading config from cwd relative path '$TARGET_ENV'." >&2 + elif [[ -f .env ]]; then + TARGET_ENV=".env" + echo "WARNING: Loading deprecated config from cwd relative path '$TARGET_ENV'. Please migrate to '.mam.env'." >&2 + fi +fi + +if [[ -n "$TARGET_ENV" && -f "$TARGET_ENV" ]]; then + set -a; source "$TARGET_ENV"; set +a +fi + +# Source EARLY (before any herdr usage in run_agent) — this is what turns +# plain `herdr` into the tmux-compat shim (herdr() function) and provides +# resolve_herdr_workspace/send_keys_safe. Sourcing it late meant the +# has-session pre-flight check below used to hit the real herdr binary with +# a nonexistent subcommand and always fail. +source "$SCRIPT_DIR/../lib.sh" + +# Pick an interpreter: prefer a project .venv, else python3. +pick_python() { + local py_bin + if [[ -n "${DELEGATE_JOB_PYTHON:-}" ]]; then + py_bin="$DELEGATE_JOB_PYTHON" + elif [[ -n "${AGENT_PYTHON_BIN:-}" ]] && [[ -x "$AGENT_PYTHON_BIN" ]]; then + py_bin="$AGENT_PYTHON_BIN" + elif [[ -x "${WORKDIR:-.}/.venv/bin/python" ]]; then + py_bin="${WORKDIR}/.venv/bin/python" + elif [[ -x ".venv/bin/python" ]]; then + py_bin="$(pwd)/.venv/bin/python" + else + py_bin="python3" + fi + if ! "$py_bin" -c "import paho.mqtt" 2>/dev/null; then + echo "ERROR: paho-mqtt package is missing for $py_bin." >&2 + echo " Please create a virtual environment and install it:" >&2 + echo " python3 -m venv .venv && .venv/bin/pip install -r \"$SCRIPT_DIR/requirements.txt\"" >&2 + exit 1 + fi + echo "$py_bin" +} + +REGISTRY_DIR_DEFAULT=".mam/jobs" + +usage() { + cat <<'EOF' +multi-agent-mux-delegate-job [options] + + submit --agent --prompt [--workdir ] [--agent-session