fix(issue-24): resume answers start-up dialogs for every agent, with the same switch as create
resume_session.sh called handle_startup_dialogs only when AGENT=claude and passed no agent, so a resumed agy, grok or codex sat on its directory-trust prompt exactly as the create path did before this branch. The gate is gone and the agent is passed, which is the call create already makes. --no-auto-trust / MAM_AUTO_TRUST apply to resume too. A resumed CLI shows the same trust dialog as a fresh one, in the same directory, with the same consequence for codex (trusting loads the project's local config, hooks and exec policy), and handle_startup_dialogs already reads MAM_AUTO_TRUST - so the only question was whether resume exposes the switch or silently inherits the caller's environment. Exposing it keeps the two skills' contracts identical. The tests run the shipped create/stop/resume scripts against the mock herdr. The mock's pane always shows a ready banner, so they fix the call resume makes (session, timeout, agent, MAM_AUTO_TRUST) by overriding the handler in the sandbox copy of lib.sh, plus one unmodified run showing the real handler is reached for a non-claude agent; which keys a dialog gets is fixed on the create side (S4-S6). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
ce802d21c8
commit
0dfbb2cc8a
3 files changed
+138
-15
No files matched your search
@@ -101,17 +101,11 @@ case "$AGENT" in
|
||||
esac
|
||||
|
||||
# 4. Spawn new herdr session + run agent with the saved id
|
||||
case "$AGENT" in
|
||||
claude)
|
||||
START_CMD="herdr new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\""
|
||||
eval "$START_CMD"
|
||||
# auto-handle trust / bypass dialogs
|
||||
handle_startup_dialogs "$SESSION_NAME" 20
|
||||
;;
|
||||
agy|hermes|grok|opencode|codex)
|
||||
eval "herdr new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\""
|
||||
;;
|
||||
esac
|
||||
eval "herdr new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\""
|
||||
# Issue #24: start-up dialogs (trust prompts, blocking notices) are answered for
|
||||
# every agent from the adapter's signatures - the same call create makes.
|
||||
# --no-auto-trust leaves a directory-trust dialog to a human (MAM_AUTO_TRUST=0).
|
||||
handle_startup_dialogs "$SESSION_NAME" 20 "$AGENT"
|
||||
|
||||
# 4. Update agent-sessions.yaml: status running, last_visible_status
|
||||
# (Also automatically publishes a `progress --detail "resumed"` event to the multi-agent-mux-delegate-job registry if a delegate_job_id exists)
|
||||
|
||||
@@ -13,6 +13,8 @@ Usage: $0 --workspace <path> --agent <claude|agy|hermes|grok|opencode|codex> --s
|
||||
|
||||
Options:
|
||||
--herdr-session NAME specify isolated herdr session name (alias: --herdr-server)
|
||||
--no-auto-trust do not answer a start-up directory-trust dialog (default: answer it;
|
||||
other blocking notices are always answered) - same switch as create
|
||||
--dry-run Simulates resume flow (resolves binary, environment) without writing
|
||||
any updates to YAML or DB. Safe to execute inside active write transactions.
|
||||
EOF
|
||||
@@ -25,6 +27,10 @@ HERDR_SERVER_OPT=""
|
||||
HERDR_WORKSPACE_OPT=""
|
||||
|
||||
DRY_RUN=0
|
||||
# Issue #24: a resumed CLI shows the same directory-trust dialog as a fresh
|
||||
# one, so resume takes the same switch as create (MAM_AUTO_TRUST is what
|
||||
# handle_startup_dialogs reads).
|
||||
AUTO_TRUST=1
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
@@ -33,6 +39,8 @@ while [ $# -gt 0 ]; do
|
||||
--session) SESSION_NAME="$2"; shift 2 ;;
|
||||
--herdr-session|--herdr-server) HERDR_SERVER_OPT="$2"; shift 2 ;;
|
||||
--herdr-workspace) HERDR_WORKSPACE_OPT="$2"; shift 2 ;;
|
||||
--auto-trust) AUTO_TRUST=1; shift ;;
|
||||
--no-auto-trust) AUTO_TRUST=0; shift ;;
|
||||
--dry-run) DRY_RUN=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "ERROR: unknown arg: $1" >&2; exit 2 ;;
|
||||
@@ -46,6 +54,7 @@ case "$AGENT" in
|
||||
*) echo "ERROR: unsupported agent: $AGENT" >&2; exit 2 ;;
|
||||
esac
|
||||
[ -n "$SESSION_NAME" ] || { echo "ERROR: --session required" >&2; exit 2; }
|
||||
export MAM_AUTO_TRUST="$AUTO_TRUST"
|
||||
|
||||
# Issue #21 phase 2 (A11): a session resumes only in the directory it ran in
|
||||
# (its conversation is keyed by that cwd; a class A agent would otherwise start
|
||||
@@ -226,10 +235,10 @@ fi
|
||||
|
||||
# 4. Spawn new agent session (delegates to herdr translation shim)
|
||||
_herdr new-session -d -s "$SESSION_NAME" -c "$WORKSPACE" "$CP_PREFIX$CMD_FULL"
|
||||
if [ "$AGENT" = "claude" ]; then
|
||||
# auto-handle trust / bypass dialogs
|
||||
handle_startup_dialogs "$SESSION_NAME" 20
|
||||
fi
|
||||
# Issue #24: every agent, the same call create makes. The signatures and accept
|
||||
# keys come from the agent adapter; this used to run only for claude, so a
|
||||
# resumed agy/grok/codex sat on its trust prompt.
|
||||
handle_startup_dialogs "$SESSION_NAME" 20 "$AGENT"
|
||||
|
||||
# Wait for TUI readiness or let it settle
|
||||
sleep 2
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
"""Issue #24, resume path: start-up dialogs are answered for every agent.
|
||||
|
||||
resume_session.sh called handle_startup_dialogs only for claude and passed no
|
||||
agent, so a resumed agy/grok/codex sat on its directory-trust prompt exactly as
|
||||
the create path did before this branch. These fix the call contract resume
|
||||
now shares with create.
|
||||
|
||||
What is real: the shipped create/stop/resume scripts run end to end against
|
||||
the mock herdr in a sandbox. What is not: the mock's pane always shows a
|
||||
ready banner, so no test here drives a real trust dialog - the create-side
|
||||
tests (test_issue23_24_startup_path.py, S4-S6) fix which keys each dialog
|
||||
gets, and R4 shows the same shipped function is now reached from resume.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
SCRIPTS = ("multi-agent-mux-create/scripts/create_session.sh",
|
||||
"multi-agent-mux-stop/scripts/stop_session.sh",
|
||||
"multi-agent-mux-resume/scripts/resume_session.sh")
|
||||
|
||||
|
||||
def _script(tmp_path, rel):
|
||||
return str(tmp_path / ".agents" / "skills" / rel)
|
||||
|
||||
|
||||
def _create_and_stop(tmp_path, agent, name):
|
||||
ws = str(tmp_path / ("ws_" + name))
|
||||
os.makedirs(ws, exist_ok=True)
|
||||
create, stop, _ = (_script(tmp_path, s) for s in SCRIPTS)
|
||||
subprocess.run(["bash", create, "--workspace", ws, "--agent", agent,
|
||||
"--role", "creator", "--session", name, "--no-onboard"],
|
||||
check=True, cwd=str(tmp_path), capture_output=True, text=True)
|
||||
subprocess.run(["bash", stop, "--session", name, "--agent", agent],
|
||||
check=True, cwd=str(tmp_path), capture_output=True, text=True)
|
||||
return ws
|
||||
|
||||
|
||||
def _resume(tmp_path, agent, name, ws, extra=()):
|
||||
_, _, resume = (_script(tmp_path, s) for s in SCRIPTS)
|
||||
return subprocess.run(["bash", resume, "--workspace", ws, "--agent", agent,
|
||||
"--session", name, *extra],
|
||||
cwd=str(tmp_path), capture_output=True, text=True)
|
||||
|
||||
|
||||
def _record_dialog_calls(tmp_path):
|
||||
"""Override handle_startup_dialogs in the SANDBOX copy of lib.sh (never the
|
||||
repo's) so the call resume makes - session, timeout, agent, MAM_AUTO_TRUST
|
||||
- is written down. Bash keeps the last definition, so appending wins."""
|
||||
log = tmp_path / "dialog_calls.log"
|
||||
lib = tmp_path / ".agents" / "skills" / "lib.sh"
|
||||
with open(lib, "a") as f:
|
||||
f.write('\nhandle_startup_dialogs() {\n'
|
||||
' printf \'%%s\\n\' "sess=$1 timeout=${2:-} agent=${3:-<none>} '
|
||||
'auto_trust=${MAM_AUTO_TRUST:-<unset>}" >> "%s"\n'
|
||||
'}\n' % log)
|
||||
return log
|
||||
|
||||
|
||||
def _calls(log):
|
||||
return log.read_text().splitlines() if log.exists() else []
|
||||
|
||||
|
||||
@pytest.mark.parametrize("agent", ["agy", "codex"])
|
||||
def test_r1_resume_answers_dialogs_for_a_non_claude_agent_and_names_it(
|
||||
mam_sandbox, mock_herdr, mock_agents, agent):
|
||||
"""R1: the gate is gone and the agent is passed - the two halves of the
|
||||
create contract resume was missing."""
|
||||
tmp_path = mam_sandbox
|
||||
name = "test-r1-" + agent
|
||||
ws = _create_and_stop(tmp_path, agent, name)
|
||||
log = _record_dialog_calls(tmp_path) # armed after create, so only resume is recorded
|
||||
res = _resume(tmp_path, agent, name, ws)
|
||||
assert res.returncode == 0, res.stdout + res.stderr
|
||||
assert _calls(log) == ["sess=%s timeout=20 agent=%s auto_trust=1" % (name, agent)]
|
||||
|
||||
|
||||
def test_r2_claude_keeps_its_dialog_handling(mam_sandbox, mock_herdr, mock_agents):
|
||||
"""R2: claude is still handled, now by the same call as everyone else."""
|
||||
tmp_path = mam_sandbox
|
||||
ws = _create_and_stop(tmp_path, "claude", "test-r2-claude")
|
||||
log = _record_dialog_calls(tmp_path)
|
||||
res = _resume(tmp_path, "claude", "test-r2-claude", ws)
|
||||
assert res.returncode == 0, res.stdout + res.stderr
|
||||
assert _calls(log) == ["sess=test-r2-claude timeout=20 agent=claude auto_trust=1"]
|
||||
|
||||
|
||||
def test_r3_no_auto_trust_reaches_the_dialog_handler(mam_sandbox, mock_herdr, mock_agents):
|
||||
"""R3: --no-auto-trust on resume sets MAM_AUTO_TRUST=0 for the handler,
|
||||
the same switch with the same meaning as create's."""
|
||||
tmp_path = mam_sandbox
|
||||
ws = _create_and_stop(tmp_path, "codex", "test-r3-codex")
|
||||
log = _record_dialog_calls(tmp_path)
|
||||
res = _resume(tmp_path, "codex", "test-r3-codex", ws, extra=("--no-auto-trust",))
|
||||
assert res.returncode == 0, res.stdout + res.stderr
|
||||
assert _calls(log) == ["sess=test-r3-codex timeout=20 agent=codex auto_trust=0"]
|
||||
|
||||
|
||||
def test_r4_the_shipped_handler_really_runs_for_a_non_claude_resume(
|
||||
mam_sandbox, mock_herdr, mock_agents):
|
||||
"""R4 (no override): with the real handle_startup_dialogs, a codex resume
|
||||
completes and the pane is read after the spawn - the handler was reached
|
||||
and returned on the ready banner. Before this change no pane read happened
|
||||
between new-session and the YAML update for a non-claude agent."""
|
||||
tmp_path = mam_sandbox
|
||||
ws = _create_and_stop(tmp_path, "codex", "test-r4-codex")
|
||||
before = len(json.load(open(mock_herdr)).get("calls", []))
|
||||
res = _resume(tmp_path, "codex", "test-r4-codex", ws)
|
||||
assert res.returncode == 0, res.stdout + res.stderr
|
||||
calls = json.load(open(mock_herdr)).get("calls", [])[before:]
|
||||
|
||||
def _has(c, *verbs): # the mock records argv with a leading `--session <name>`
|
||||
return any(tuple(c[i:i + len(verbs)]) == verbs for i in range(len(c)))
|
||||
|
||||
spawn = next(i for i, c in enumerate(calls) if _has(c, "agent", "start"))
|
||||
reads = [c for c in calls[spawn + 1:] if _has(c, "agent", "read") or _has(c, "pane", "read")]
|
||||
assert reads, "no pane read after the spawn: handle_startup_dialogs never ran\n%s" % calls
|
||||
Reference in new issue
Block a user