fix(herdr): prevent pane misattribution and prioritize idle-pane reuse in new-session

- Restrict generic agent_kind fallback in _resolve_herdr_pane_id to bare-kind searches
- Exclude 'agent' key from exact-match loop to enforce R-1 fail-closed contract
- Prioritize reusing existing idle pane on new-session to prevent unbounded pane proliferation
- Wrap final_cmd with env prefix to preserve env_flags when reusing idle pane
This commit is contained in:
2026-08-30 11:02:50 +09:00
parent ccdde63bac
commit b327bc7132
+36 -3
View File
@@ -29,7 +29,7 @@ export WORKSPACE_ROOT
# Framework semantic version. Single runtime source of truth; kept in lockstep
# with VERSIONS.md and the 8 SKILL.md frontmatters by tests/test_version_consistency.py.
# NOTE: unlike other MAM_* variables this one is intentionally NOT env-overridable.
MAM_VERSION="4.1.1"
MAM_VERSION="4.1.2"
export MAM_VERSION
AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}"
@@ -412,7 +412,7 @@ try:
if tws:
panes = [p for p in panes if p.get('workspace_id') == tws]
# ISSUE-2: exact match first, then agent-sessions.yaml lookup fallback
for key in ('label', 'name', 'agent'):
for key in ('label', 'name'):
for p in panes:
v = p.get(key)
if v and (v == tn or v == tsa):
@@ -433,7 +433,13 @@ try:
break
except Exception as e:
sys.stderr.write(f'warning: yaml load failed: {e}\n')
if agent_kind:
# BUG-1: only trust the generic agent_kind fallback when the caller's
# own target string IS the bare kind (e.g. target == 'agy'). Using a
# *derived* kind (looked up via the yaml entry matched by tn/tsa) to
# then broadly match any pane of this kind is unsound whenever another,
# unrelated same-kind session is alive in the workspace - it misattributes
# that unrelated pane as this target even though their names differ.
if agent_kind and (tn == agent_kind or tsa == agent_kind):
matching = [p.get('pane_id') for p in panes if p.get('agent') == agent_kind and p.get('pane_id')]
if len(matching) == 1:
print(matching[0])
@@ -702,6 +708,32 @@ except Exception:
ws_id=""
target_pane=""
if [ -n "$existing_ws" ]; then
# BUG-4: prefer an already-idle (agent-less) pane in the existing
# workspace over splitting a new one — avoids unbounded pane
# proliferation and 2x2 grid-layout churn when a prior agent's pane
# was left behind idle.
idle_pane=$(_real_herdr pane list 2>/dev/null | TARGET_WS="$existing_ws" python3 -c "
import sys, json, os
target_ws = os.environ.get('TARGET_WS', '')
try:
d = json.loads(sys.stdin.read())
panes = d.get('result', {}).get('panes', [])
for p in panes:
if p.get('workspace_id') == target_ws and not p.get('agent') and p.get('pane_id'):
print(p.get('pane_id'))
break
except Exception:
pass
" 2>/dev/null || echo "")
if [ -n "$idle_pane" ]; then
target_pane="$idle_pane"
if [ -n "$final_cmd" ]; then
idle_env="${env_flags//--env /}"
idle_env="${idle_env:+$idle_env }HERDR_WORKSPACE_ID=$existing_ws"
final_cmd="env $idle_env $final_cmd"
fi
else
# W2a: Determine split direction policy via pane layout
sample_pane=$(_real_herdr pane list 2>/dev/null | TARGET_WS="$existing_ws" python3 -c "
import sys, json, os
@@ -751,6 +783,7 @@ except Exception:
pass
" 2>/dev/null || echo "")
fi
fi
fi
if [ -z "$existing_ws" ] || [ -z "$target_pane" ]; then