refactor: standardize --agent option usage, improve YAML registry fallback, and fix layout falsy-zero trap (J-1)
- In stop_session.sh and update_yaml_resumed.sh: standardize explicit --agent option and use resolve_agent_type_from_registry to read agent type from YAML/DB state rather than brittle suffix-only regex inference. - Update multi-agent-mux-stop/SKILL.md, multi-agent-mux-resume/SKILL.md, multi-agent-mux-create/SKILL.md, and deploy/INSTALL.md to standardize passing --agent explicitly. - Fix J-1 in layout.py: refactor _env_int(*names, default=None) to take an explicit default parameter, eliminating the falsy-zero trap so MAM_MIN_PANE_COLS=0 is respected. - Add regression and contract tests: test_j1_env_zero_min_cols_matches_flag_zero, test_j1_env_zero_min_rows_matches_flag_zero, test_comp_stop_agent_fallback_*, test_comp_docs_stop_examples_pass_agent. - Verified 100% UNANIMOUS PASS from Planner claude and Reviewers claude and cline.
This commit is contained in:
@@ -979,6 +979,34 @@ print(json.dumps(d, ensure_ascii=False))
|
||||
PYEOF
|
||||
}
|
||||
|
||||
# resolve_agent_type_from_registry <session_name>
|
||||
#
|
||||
# 레지스트리(YAML/DB)에 기록된 사실로 에이전트 종류를 해석한다. 우선순위는
|
||||
# lib_py.agents.registry.agent_of_row 의 계약을 그대로 따른다:
|
||||
# ① row['agent'] 명시 필드
|
||||
# ② 세션명 접미사 (*-{creator,planner,reviewer}-<agent> 및 *-<agent>)
|
||||
# ③ pane.cmd (정확히 일치하거나 .../<agent> 바이너리 경로)
|
||||
# 성공하면 에이전트명을 stdout 에 출력하고 0 을, 셋 다 실패하면 아무것도
|
||||
# 출력하지 않고 1 을 반환한다. 오류 메시지는 호출자가 소유한다 — 각 스크립트가
|
||||
# 문서화한 종료 코드를 그대로 유지하기 위해서다.
|
||||
#
|
||||
# NOTE: agent_of_row 의 match_cmd=True 는 "비-입양 조회" 계약이다. reconcile.sh
|
||||
# 입양 루프는 이 헬퍼를 쓰면 안 된다 (3aee63cf §1.2 실측 반증).
|
||||
resolve_agent_type_from_registry() {
|
||||
local name="$1"
|
||||
MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$name" python3 -c "
|
||||
import os, json, sys
|
||||
from lib_py.agents.registry import agent_of_row
|
||||
name = os.environ['SESSION_NAME']
|
||||
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
|
||||
row = next((s for s in d.get('herdr_sessions', []) if s.get('name') == name), {})
|
||||
resolved = agent_of_row(row, session_name=name)
|
||||
if not resolved:
|
||||
sys.exit(1)
|
||||
print(resolved)
|
||||
"
|
||||
}
|
||||
|
||||
# Despite the name (kept for caller compatibility — resume/stop/update_yaml_resumed
|
||||
# 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
|
||||
|
||||
@@ -172,24 +172,34 @@ def compute_2xk_layout(
|
||||
return LayoutDecision(target_pane_id=rightmost_top_pane.pane_id, direction="right", reason="new_column_right")
|
||||
|
||||
|
||||
def _env_int(*names: str) -> Optional[int]:
|
||||
"""First non-empty env var among *names, parsed as int. Bad values are
|
||||
ignored rather than raised: a typo in an operator's shell must not take the
|
||||
whole layout call down (lib.sh would silently fall back to 'right')."""
|
||||
def _env_int(*names: str, default: Optional[int] = None) -> Optional[int]:
|
||||
"""First *valid* int among the env vars in *names*, else `default`.
|
||||
|
||||
`default` is an explicit parameter rather than an `or` at the call site so a
|
||||
legitimate 0 survives (MAM_MIN_PANE_COLS=0 means 0, not the 60 default).
|
||||
|
||||
An unparsable value is skipped rather than raised or treated as terminal: a
|
||||
typo in an operator's shell must not take the whole layout call down (lib.sh
|
||||
would silently fall back to 'right'), and must not shadow a later candidate
|
||||
that IS set correctly -- MAM_MIN_COLS is a legacy alias while
|
||||
MAM_MIN_PANE_COLS is the name .mam.env.example documents, so aborting on the
|
||||
first bad value would discard the documented setting. Empty values already
|
||||
fell through; this makes invalid values behave the same way.
|
||||
"""
|
||||
for n in names:
|
||||
raw = os.environ.get(n, "").strip()
|
||||
if raw:
|
||||
try:
|
||||
return int(raw)
|
||||
except ValueError:
|
||||
return None
|
||||
return None
|
||||
continue
|
||||
return default
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Compute 2xK grid TUI layout split direction")
|
||||
parser.add_argument("--min-cols", type=int, default=_env_int("MAM_MIN_COLS", "MAM_MIN_PANE_COLS") or 60)
|
||||
parser.add_argument("--min-rows", type=int, default=_env_int("MAM_MIN_ROWS", "MAM_MIN_PANE_ROWS") or 20)
|
||||
parser.add_argument("--min-cols", type=int, default=_env_int("MAM_MIN_COLS", "MAM_MIN_PANE_COLS", default=60))
|
||||
parser.add_argument("--min-rows", type=int, default=_env_int("MAM_MIN_ROWS", "MAM_MIN_PANE_ROWS", default=20))
|
||||
parser.add_argument("--max-cols", type=int, default=_env_int("MAM_MAX_COLS", "MAM_MAX_PANE_COLS"))
|
||||
parser.add_argument("--sample-pane", type=str, default=None)
|
||||
parser.add_argument("--json", action="store_true", help="Output full JSON decision")
|
||||
|
||||
@@ -143,7 +143,7 @@ herdr_sessions:
|
||||
|
||||
```bash
|
||||
WORKSPACE=/path/to/project
|
||||
AGENT=claude # or agy
|
||||
AGENT=claude # claude | agy | hermes | cline — always pass it explicitly
|
||||
source .agents/skills/lib.sh
|
||||
SESSION_NAME="$(derive_session_name "$WORKSPACE" "$AGENT")"
|
||||
|
||||
@@ -168,7 +168,7 @@ case "$AGENT" in
|
||||
agy)
|
||||
herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "agy --dangerously-skip-permissions"
|
||||
;;
|
||||
*) echo "ERROR: --agent must be claude or agy, got: $AGENT"; exit 2 ;;
|
||||
*) echo "ERROR: --agent must be claude, agy, hermes or cline, got: $AGENT"; exit 2 ;;
|
||||
esac
|
||||
|
||||
# 3. Wait for agent TUI to be ready (varies: claude ~5s, agy ~3s)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# create_session.sh — multi-agent-mux-create 의 부속 스크립트
|
||||
# Usage:
|
||||
# bash create_session.sh --workspace <path> --agent <claude|agy> --role <role> [--session <name>] [--wrapper]
|
||||
# bash create_session.sh --workspace <path> --agent <claude|agy|hermes|cline> --role <role> [--session <name>] [--wrapper]
|
||||
#
|
||||
# 동작:
|
||||
# 1) preflight: herdr/claude/agy 가용성, workspace 존재
|
||||
@@ -20,7 +20,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
_lib_sh="$(cd "$_script_dir/../.." 2>/dev/null || pwd)/lib.sh"
|
||||
_lib_sh="$(cd "$_script_dir/../.." && pwd)/lib.sh"
|
||||
[ -f "$_lib_sh" ] || _lib_sh="${WORKSPACE_ROOT:-$PWD}/.agents/skills/lib.sh"
|
||||
source "$_lib_sh"
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ If both are empty → the workspace has no conversation yet. Fall back to `multi
|
||||
|
||||
```bash
|
||||
WORKSPACE=/path/to/project
|
||||
AGENT=claude # or agy or hermes
|
||||
AGENT=claude # claude | agy | hermes | cline — pass it explicitly
|
||||
SESSION_NAME=<workspace>-creator-<agent> # same convention as multi-agent-mux-create
|
||||
|
||||
# Resolve the isolated herdr server name & load common utils
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# resolve_session_id.sh — multi-agent-mux-resume 의 부속 스크립트
|
||||
# Usage:
|
||||
# bash resolve_session_id.sh --workspace <path> --agent <claude|agy>
|
||||
# bash resolve_session_id.sh --workspace <path> --agent <claude|agy|hermes|cline>
|
||||
# 출력: stdout 으로 UUID 한 줄 (없으면 빈 줄 + exit 0)
|
||||
#
|
||||
# P0-C: 전역 agent_identities 를 즉시 반환하지 않는다. lib.sh::find_workspace_uuid
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LIB_SH="$(cd "$SCRIPT_DIR/../.." 2>/dev/null || pwd)/lib.sh"
|
||||
LIB_SH="$(cd "$SCRIPT_DIR/../.." && pwd)/lib.sh"
|
||||
[ -f "$LIB_SH" ] || LIB_SH="${WORKSPACE_ROOT:-$PWD}/.agents/skills/lib.sh"
|
||||
source "$LIB_SH"
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
# resume UUID 를 per-row own id (claude_session_id_own / agy_conversation_id_own)
|
||||
# 에 박는다 — agent_identities 전역은 더 이상 primary 아님 (cache 로 강등, P0-C/단계 e).
|
||||
#
|
||||
# Usage: bash update_yaml_resumed.sh --session <name> --uuid <id> [--agent claude|agy]
|
||||
# Usage: bash update_yaml_resumed.sh --session <name> --uuid <id> [--agent claude|agy|hermes|cline]
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/lib.sh"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 --session <name> --uuid <id> [--agent claude|agy]
|
||||
Usage: $0 --session <name> --uuid <id> [--agent claude|agy|hermes|cline]
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -40,15 +40,15 @@ done
|
||||
HERDR_SESSION_NAME="$(resolve_herdr_session "$SESSION_NAME" "${WORKSPACE:-}")"
|
||||
export HERDR_SESSION_NAME
|
||||
|
||||
# --agent 미지정 시 이름 suffix 로 fallback (P1-F: 가능하면 --agent 명시)
|
||||
# --agent 미지정 시 레지스트리 기록으로 해석 (B-21).
|
||||
# ① row['agent'] → ② 세션명 접미사 → ③ pane.cmd 순. 셋 다 실패하면
|
||||
# 종전과 동일하게 exit 2 (헤더 :27-30 의 종료 코드 계약 유지).
|
||||
if [ -z "$AGENT" ]; then
|
||||
case "$SESSION_NAME" in
|
||||
*-creator-claude|*-planner-claude|*-reviewer-claude) AGENT=claude ;;
|
||||
*-creator-agy|*-planner-agy|*-reviewer-agy) AGENT=agy ;;
|
||||
*-creator-hermes|*-planner-hermes|*-reviewer-hermes) AGENT=hermes ;;
|
||||
*-creator-cline|*-planner-cline|*-reviewer-cline) AGENT=cline ;;
|
||||
*) echo "ERROR: cannot infer agent from '$SESSION_NAME'; pass --agent" >&2; exit 2 ;;
|
||||
esac
|
||||
AGENT="$(resolve_agent_type_from_registry "$SESSION_NAME")" || AGENT=""
|
||||
[ -n "$AGENT" ] || {
|
||||
echo "ERROR: cannot infer agent from '$SESSION_NAME'; pass --agent" >&2
|
||||
exit 2
|
||||
}
|
||||
fi
|
||||
|
||||
if [ -z "$ROLE" ]; then
|
||||
|
||||
@@ -37,6 +37,7 @@ The stop command is always **graceful by default**:
|
||||
|
||||
```bash
|
||||
SESSION_NAME=<workspace>-creator-<agent> # convention
|
||||
AGENT=claude # claude | agy | hermes | cline — always pass it
|
||||
AGENT_SESSIONS_YAML=.mam/agent-sessions.yaml
|
||||
|
||||
# 1) Session is registered?
|
||||
@@ -66,20 +67,27 @@ fi
|
||||
```bash
|
||||
# 1. Stop gracefully (default — captures ID, shuts down safely, status=stopped)
|
||||
bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \
|
||||
--session "$SESSION_NAME"
|
||||
--session "$SESSION_NAME" --agent "$AGENT"
|
||||
|
||||
# 2. Stop gracefully + record a custom stop reason
|
||||
bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \
|
||||
--session "$SESSION_NAME" --reason api_error
|
||||
--session "$SESSION_NAME" --agent "$AGENT" --reason api_error
|
||||
|
||||
# 3. Stop gracefully + clean up on-disk conversation (DANGEROUS)
|
||||
# — this prevents any future resume (status=terminated, resumable=false).
|
||||
bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \
|
||||
--session "$SESSION_NAME" --purge-conversation
|
||||
--session "$SESSION_NAME" --agent "$AGENT" --purge-conversation
|
||||
```
|
||||
|
||||
**Idempotency**: if the row is already `status: stopped`, the script prints `already stopped (...)` and exits 0 — re-running is a safe no-op.
|
||||
|
||||
**`--agent` is the standard.** Pass it on every invocation. If omitted, the script
|
||||
resolves the agent from the registry record — the row's `agent` field, then the
|
||||
session-name suffix, then `pane.cmd` — and exits 2 if none of the three resolve.
|
||||
The fallback exists for recovery, not as the normal calling convention: a session
|
||||
whose name carries no agent suffix (e.g. `agy-creator-01`) is only resolvable
|
||||
while its registry row survives.
|
||||
|
||||
### State machine
|
||||
|
||||
```
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
# 옵션:
|
||||
# --session <name> — 대상 세션 (필수)
|
||||
# --agent <type> — claude | agy | hermes | cline
|
||||
# (미지정 시 세션명 접미사로 추론; 추론 실패 시 exit 2)
|
||||
# (권장: 항상 명시. 미지정 시 레지스트리 기록으로
|
||||
# 해석 — agent 필드 → 세션명 접미사 → pane.cmd;
|
||||
# 셋 다 실패하면 exit 2)
|
||||
# --reason <reason> — 상태 전이 사유 (stop_reason). 기본값 manual_stop
|
||||
# --purge-conversation — 디스크의 conversation artifact 까지 삭제.
|
||||
# status=terminated, resumable=false 로 전이하며
|
||||
@@ -32,7 +34,7 @@ set -euo pipefail
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
_lib_sh="$(cd "$_script_dir/../.." 2>/dev/null || pwd)/lib.sh"
|
||||
_lib_sh="$(cd "$_script_dir/../.." && pwd)/lib.sh"
|
||||
[ -f "$_lib_sh" ] || _lib_sh="${WORKSPACE_ROOT:-$PWD}/.agents/skills/lib.sh"
|
||||
source "$_lib_sh"
|
||||
|
||||
@@ -43,8 +45,9 @@ Usage: $0 --session <name> [--agent claude|agy|hermes|cline] [--reason <reason>]
|
||||
|
||||
Arguments:
|
||||
--session <name> — target session name (required)
|
||||
--agent <type> — claude | agy | hermes | cline
|
||||
(inferred from the session-name suffix when omitted)
|
||||
--agent <type> — claude | agy | hermes | cline (recommended: always pass it)
|
||||
(falls back to the registry record: agent field ->
|
||||
session-name suffix -> pane.cmd)
|
||||
--reason <reason> — stop_reason field (default: manual_stop)
|
||||
--purge-conversation — also delete on-disk conversation artifacts;
|
||||
status becomes terminated and resume is impossible
|
||||
@@ -97,15 +100,15 @@ fi
|
||||
HERDR_SESSION_NAME="$(resolve_herdr_workspace "$SESSION_NAME" "${WORKSPACE:-$WORKSPACE_ROOT}")"
|
||||
export HERDR_SESSION_NAME
|
||||
|
||||
# --agent 미지정 시 이름 suffix 로 fallback (P1-F)
|
||||
# --agent 미지정 시 레지스트리 기록으로 해석 (B-21).
|
||||
# ① row['agent'] → ② 세션명 접미사 → ③ pane.cmd 순. 셋 다 실패하면
|
||||
# 종전과 동일하게 exit 2 (헤더 :27-30 의 종료 코드 계약 유지).
|
||||
if [ -z "$AGENT" ]; then
|
||||
case "$SESSION_NAME" in
|
||||
*-creator-claude|*-planner-claude|*-reviewer-claude) AGENT=claude ;;
|
||||
*-creator-agy|*-planner-agy|*-reviewer-agy) AGENT=agy ;;
|
||||
*-creator-hermes|*-planner-hermes|*-reviewer-hermes) AGENT=hermes ;;
|
||||
*-creator-cline|*-planner-cline|*-reviewer-cline) AGENT=cline ;;
|
||||
*) echo "ERROR: cannot infer agent from '$SESSION_NAME'; pass --agent" >&2; exit 2 ;;
|
||||
esac
|
||||
AGENT="$(resolve_agent_type_from_registry "$SESSION_NAME")" || AGENT=""
|
||||
[ -n "$AGENT" ] || {
|
||||
echo "ERROR: cannot infer agent from '$SESSION_NAME'; pass --agent" >&2
|
||||
exit 2
|
||||
}
|
||||
fi
|
||||
|
||||
# 세션이 YAML 에 있는지 + 해당 row 의 워크스페이스 cwd 및 delegate_job_id 추출.
|
||||
|
||||
Reference in New Issue
Block a user