fix(resume): no guess with two same-type rows in a cwd; refuse another row's id before spawning; the running branch uses the same decision (Issue #63)
This commit is contained in:
1 parent
3a2219454e
commit
fa93cc5622
4 files changed
+290
-27
No files matched your search
@@ -4,9 +4,9 @@
|
||||
# bash resolve_session_id.sh --workspace <path> --agent <claude|agy|hermes|grok|opencode|codex>
|
||||
# 출력: stdout 으로 UUID 한 줄 (없으면 빈 줄 + exit 0)
|
||||
#
|
||||
# P0-C: 전역 agent_identities 를 즉시 반환하지 않는다. lib.sh::find_workspace_uuid
|
||||
# 가 워크스페이스 격리된 해결 경로(per-row own id -> 디스크 스캔 -> cwd 일치하는
|
||||
# cache)만 사용. 다른 워크스페이스의 UUID 를 절대 반환하지 않음.
|
||||
# P0-C / Issue #63: 전역 agent_identities 를 즉시 반환하지 않는다. lib.sh::mam_session_ids
|
||||
# 가 워크스페이스 격리된 해결 경로(per-row own id -> #40 active capture / 디스크 스캔)만 사용.
|
||||
# 다른 워크스페이스나 다른 행이 기록한 UUID 를 절대 반환하지 않음.
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/lib.sh"
|
||||
@@ -22,13 +22,17 @@ EOF
|
||||
WORKSPACE=""
|
||||
AGENT=""
|
||||
SESSION_NAME=""
|
||||
HERDR_ALIVE="0"
|
||||
PANE_PID=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--workspace) WORKSPACE="$2"; shift 2 ;;
|
||||
--agent) AGENT="$2"; shift 2 ;;
|
||||
--session) SESSION_NAME="$2"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
--workspace) WORKSPACE="$2"; shift 2 ;;
|
||||
--agent) AGENT="$2"; shift 2 ;;
|
||||
--session) SESSION_NAME="$2"; shift 2 ;;
|
||||
--herdr-alive) HERDR_ALIVE="$2"; shift 2 ;;
|
||||
--pane-pid) PANE_PID="$2"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "ERROR: unknown arg: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
@@ -40,4 +44,6 @@ case "$AGENT" in
|
||||
*) echo "ERROR: --agent must be claude, agy, hermes, grok, opencode, or codex" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
find_workspace_uuid "$WORKSPACE" "$AGENT" "$SESSION_NAME"
|
||||
mam_session_ids resume --session "$SESSION_NAME" --agent "$AGENT" \
|
||||
--workspace "$(mam_abs_workspace "$WORKSPACE")" \
|
||||
--herdr-alive "$HERDR_ALIVE" --pane-pid "$PANE_PID"
|
||||
@@ -107,9 +107,33 @@ if [ "$cp_rc" -ne 0 ] || [ -z "$CP_JSON" ]; then
|
||||
CP_JSON=""
|
||||
fi
|
||||
|
||||
# 1. Resolve the session id
|
||||
UUID=$(bash "$(dirname "${BASH_SOURCE[0]}")/resolve_session_id.sh" \
|
||||
--workspace "$WORKSPACE" --agent "$AGENT" --session "$SESSION_NAME")
|
||||
if [ -n "$HERDR_SERVER_OPT" ]; then
|
||||
export HERDR_SESSION_NAME="$HERDR_SERVER_OPT"
|
||||
else
|
||||
HERDR_SESSION_NAME="$(resolve_herdr_session "$SESSION_NAME" "$WORKSPACE")"
|
||||
export HERDR_SESSION_NAME
|
||||
fi
|
||||
|
||||
if [ -n "$HERDR_WORKSPACE_OPT" ]; then
|
||||
export MAM_WS_LABEL="$HERDR_WORKSPACE_OPT"
|
||||
else
|
||||
export MAM_WS_LABEL="$(resolve_herdr_workspace "$SESSION_NAME" "$WORKSPACE")"
|
||||
fi
|
||||
|
||||
HERDR_ALIVE=0
|
||||
PANE_PID_NOW=""
|
||||
if herdr has-session -t "$SESSION_NAME" 2>/dev/null; then
|
||||
HERDR_ALIVE=1
|
||||
PANE_PID_NOW=$(herdr list-panes -t "$SESSION_NAME" -F '#{pane_pid}' 2>/dev/null | head -1 || true)
|
||||
fi
|
||||
|
||||
# 1. Resolve the session id (Issue #63: own id first, then #40 capture when
|
||||
# herdr is alive, or fresh spawn when 2+ same-type rows share the cwd)
|
||||
RESUME_OUT=$(mam_session_ids resume --shell \
|
||||
--workspace "$(mam_abs_workspace "$WORKSPACE")" --agent "$AGENT" --session "$SESSION_NAME" \
|
||||
--herdr-alive "$HERDR_ALIVE" --pane-pid "$PANE_PID_NOW")
|
||||
eval "$RESUME_OUT"
|
||||
UUID="$RESUME_UUID"
|
||||
|
||||
# ISSUE-3: agy/hermes/opencode (Class A) are spawned without a UUID - their
|
||||
# own conversation id is only assigned by the agent itself on its first turn.
|
||||
@@ -130,21 +154,8 @@ if [ -z "$UUID" ]; then
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ -n "$HERDR_SERVER_OPT" ]; then
|
||||
export HERDR_SESSION_NAME="$HERDR_SERVER_OPT"
|
||||
else
|
||||
HERDR_SESSION_NAME="$(resolve_herdr_session "$SESSION_NAME" "$WORKSPACE")"
|
||||
export HERDR_SESSION_NAME
|
||||
fi
|
||||
|
||||
if [ -n "$HERDR_WORKSPACE_OPT" ]; then
|
||||
export MAM_WS_LABEL="$HERDR_WORKSPACE_OPT"
|
||||
else
|
||||
export MAM_WS_LABEL="$(resolve_herdr_workspace "$SESSION_NAME" "$WORKSPACE")"
|
||||
fi
|
||||
|
||||
# 2. If herdr is alive, print warning or attach.
|
||||
if herdr has-session -t "$SESSION_NAME" 2>/dev/null; then
|
||||
if [ "$HERDR_ALIVE" = "1" ]; then
|
||||
if [ "${DRY_RUN:-0}" = "1" ]; then
|
||||
echo "[dry-run] herdr '$SESSION_NAME' already running — nothing to validate"
|
||||
exit 0
|
||||
@@ -153,7 +164,7 @@ if herdr has-session -t "$SESSION_NAME" 2>/dev/null; then
|
||||
# Just update YAML to make sure it's set to running
|
||||
CONTROL_PLANE_JSON="$CP_JSON" bash "$(dirname "${BASH_SOURCE[0]}")/update_yaml_resumed.sh" \
|
||||
--session "$SESSION_NAME" --uuid "$UUID" --agent "$AGENT" --workspace "$WORKSPACE" \
|
||||
--herdr-session "$HERDR_SESSION_NAME" \
|
||||
--herdr-session "$HERDR_SESSION_NAME" --already-running \
|
||||
${HERDR_WORKSPACE_OPT:+--herdr-workspace "$HERDR_WORKSPACE_OPT"}
|
||||
exit 0
|
||||
fi
|
||||
@@ -252,9 +263,12 @@ handle_startup_dialogs "$SESSION_NAME" 20 "$AGENT"
|
||||
sleep 2
|
||||
|
||||
# 5. Update agent-sessions.yaml: status running, last_visible_status
|
||||
DROP_FLAG=""
|
||||
[ "${DROP_STALE_ID:-0}" = "1" ] && DROP_FLAG="--drop-stale-id"
|
||||
CONTROL_PLANE_JSON="$CP_JSON" bash "$(dirname "${BASH_SOURCE[0]}")/update_yaml_resumed.sh" \
|
||||
--session "$SESSION_NAME" --uuid "$UUID" --agent "$AGENT" --workspace "$WORKSPACE" \
|
||||
--herdr-session "$HERDR_SESSION_NAME" --cmd-full "$CMD_FULL" \
|
||||
${DROP_FLAG:+$DROP_FLAG} \
|
||||
${HERDR_WORKSPACE_OPT:+--herdr-workspace "$HERDR_WORKSPACE_OPT"}
|
||||
|
||||
echo "Successfully resumed $SESSION_NAME ($AGENT)"
|
||||
@@ -27,6 +27,8 @@ ROLE=""
|
||||
HERDR_SERVER_OPT=""
|
||||
HERDR_WORKSPACE_OPT=""
|
||||
CMD_FULL_OPT=""
|
||||
ALREADY_RUNNING=0
|
||||
DROP_STALE_ID=0
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
@@ -38,6 +40,8 @@ while [ $# -gt 0 ]; do
|
||||
--herdr-session|--herdr-server) HERDR_SERVER_OPT="$2"; shift 2 ;;
|
||||
--herdr-workspace) HERDR_WORKSPACE_OPT="$2"; shift 2 ;;
|
||||
--cmd-full) CMD_FULL_OPT="$2"; shift 2 ;;
|
||||
--already-running) ALREADY_RUNNING=1; shift ;;
|
||||
--drop-stale-id) DROP_STALE_ID=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "ERROR: unknown arg: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
@@ -110,6 +114,7 @@ atomic_dump_yaml "$AGENT_SESSIONS_YAML" \
|
||||
NOW_EPOCH="$NOW_EPOCH" TARGET_WORKSPACE="${WORKSPACE:-$WORKSPACE_ROOT}" ROLE="$ROLE" \
|
||||
PANE_PID="$PANE_PID" CHILD_PID="$CHILD_PID" HERDR_SERVER_OPT_EXPLICIT="${HERDR_SERVER_OPT_EXPLICIT:-0}" \
|
||||
MAM_WS_LABEL="$MAM_WS_LABEL" MAM_WS_LABEL_EXPLICIT="${MAM_WS_LABEL_EXPLICIT:-0}" \
|
||||
ALREADY_RUNNING="$ALREADY_RUNNING" DROP_STALE_ID="$DROP_STALE_ID" \
|
||||
CMD_FULL_OPT="$CMD_FULL_OPT" <<'PYEOF'
|
||||
name = os.environ['SESSION_NAME']
|
||||
uuid = os.environ['UUID']
|
||||
@@ -173,7 +178,17 @@ if _cp_raw and not isinstance(target.get('control_plane'), dict):
|
||||
target['control_plane'] = {k: _cp[k] for k in ('workspace_root', 'agent_sessions_yaml', 'mam_jobs_dir', 'delegate_job_logs_dir')}
|
||||
except Exception:
|
||||
pass
|
||||
if not uuid:
|
||||
stale_dropped = ''
|
||||
if not uuid and os.environ.get('DROP_STALE_ID') == '1':
|
||||
from lib_py.agents.autopin import claimed_elsewhere
|
||||
from lib_py.agents.registry import get_adapter
|
||||
_adapter = get_adapter(agent)
|
||||
_own_key = _adapter.own_key if _adapter else ''
|
||||
_old = (target.get(_own_key) or '').strip() if _own_key else ''
|
||||
if _old and _old not in set(claimed_elsewhere(target, d)):
|
||||
target[_own_key] = None
|
||||
stale_dropped = _old
|
||||
if not uuid and os.environ.get('ALREADY_RUNNING') != '1':
|
||||
# ISSUE-3 (challenge c4b0a075): a Class A fresh-spawn resume re-arms
|
||||
# discovery from scratch - the resumed agent has not spoken yet, so any
|
||||
# on-disk conversation older than THIS resume must not be auto-pinned to
|
||||
@@ -196,6 +211,10 @@ target.pop('stop_reason', None)
|
||||
target.pop('resumable', None)
|
||||
if uuid:
|
||||
target['last_visible_status'] = f'resumed conversation {uuid} at {now}'
|
||||
elif os.environ.get('ALREADY_RUNNING') == '1':
|
||||
target['last_visible_status'] = f'already running; no id recorded at {now}'
|
||||
elif stale_dropped:
|
||||
target['last_visible_status'] = f'resumed (fresh spawn, recorded id {stale_dropped} no longer verified) at {now}'
|
||||
else:
|
||||
# ISSUE-3 Class A fresh-spawn resume: no conversation id existed yet.
|
||||
target['last_visible_status'] = f'resumed (fresh spawn, no prior turn) at {now}'
|
||||
|
||||
Reference in new issue
Block a user