"""Issue #68: worktree_prepare.sh onboards by default (--no-onboard skips), and create_session.sh --no-onboard warns for post-discovered-id agents (adapter id_lifecycle == POST_DISCOVERED). """ from __future__ import annotations import json import os import re import shlex import subprocess import sys import time from pathlib import Path import pytest import yaml REPO_ROOT = Path(__file__).resolve().parent.parent SKILLS = REPO_ROOT / ".agents" / "skills" if str(SKILLS) not in sys.path: sys.path.insert(0, str(SKILLS)) from lib_py.agents.base import POST_DISCOVERED # noqa: E402 from lib_py.agents.registry import get_adapter # noqa: E402 from test_issue63_same_cwd_ids import _mock_herdr_agent, _read_rows, _row, _write_rows # noqa: E402 ALL_AGENTS = ("claude", "agy", "hermes", "grok", "opencode", "codex") EXPECTED_POST_DISCOVERED = { "agy": 1, "codex": 1, "hermes": 1, "opencode": 1, "claude": 0, "grok": 0, } UUID_RE = re.compile(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", re.I) def _norm_uuid(text: str) -> str: return UUID_RE.sub("", text) def _warn_lines(stderr: str) -> list[str]: return [l for l in stderr.splitlines() if l.startswith("WARNING: --no-onboard:")] @pytest.mark.parametrize("agent", ALL_AGENTS) def test_facts_expose_post_discovered(agent): env = dict(os.environ, PYTHONPATH=f"{SKILLS}:{os.environ.get('PYTHONPATH', '')}") res = subprocess.run( [sys.executable, "-m", "lib_py.agents", "facts", agent], capture_output=True, text=True, check=True, env=env, ) eval_res = subprocess.run( ["bash", "-c", f"set -euo pipefail; eval {shlex.quote(res.stdout)}; echo \"$MAM_ID_POST_DISCOVERED\""], capture_output=True, text=True, check=True, ) val = int(eval_res.stdout.strip()) adapter = get_adapter(agent) assert val == (1 if adapter.id_lifecycle == POST_DISCOVERED else 0) assert val == EXPECTED_POST_DISCOVERED[agent] @pytest.mark.parametrize("agent", ALL_AGENTS) def test_no_onboard_warning_dry_run(mam_sandbox, mock_herdr, mock_agents, monkeypatch, agent): monkeypatch.setenv("XAI_API_KEY", "test-key") ws = mam_sandbox / f"ws-dry-{agent}" ws.mkdir(parents=True, exist_ok=True) create_sh = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh" res = subprocess.run( ["bash", str(create_sh), "--dry-run", "--no-onboard", "--workspace", str(ws), "--agent", agent, "--role", "creator", "--session", f"t68-{agent}"], capture_output=True, text=True, cwd=str(mam_sandbox), timeout=60, ) assert res.returncode == 0, res.stdout + res.stderr warns = _warn_lines(res.stderr) assert len(warns) == EXPECTED_POST_DISCOVERED[agent], res.stderr if warns: assert "Issue #63" in warns[0] and agent in warns[0] # Control: --dry-run with default onboarding emits 0 warning lines for all agents ctl_res = subprocess.run( ["bash", str(create_sh), "--dry-run", "--workspace", str(ws), "--agent", agent, "--role", "creator", "--session", f"t68-ctl-{agent}"], capture_output=True, text=True, cwd=str(mam_sandbox), timeout=60, ) assert ctl_res.returncode == 0, ctl_res.stdout + ctl_res.stderr assert _warn_lines(ctl_res.stderr) == [], ctl_res.stderr @pytest.mark.parametrize("agent", ALL_AGENTS) def test_no_onboard_warning_keeps_exit_and_row(mam_sandbox, mock_herdr, mock_agents, monkeypatch, agent): monkeypatch.setenv("XAI_API_KEY", "test-key") ws = mam_sandbox / f"ws-real-{agent}" ws.mkdir(parents=True, exist_ok=True) session_name = f"t68-{agent}" create_sh = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh" lib_sh = mam_sandbox / ".agents" / "skills" / "lib.sh" ws_slug = subprocess.run( ["bash", "-c", f'source "{lib_sh}" && derive_workspace_slug "{ws}"'], capture_output=True, text=True, check=True, cwd=str(mam_sandbox), ).stdout.strip() res = subprocess.run( ["bash", str(create_sh), "--no-onboard", "--workspace", str(ws), "--agent", agent, "--role", "creator", "--session", session_name], capture_output=True, text=True, cwd=str(mam_sandbox), timeout=120, ) # 1. Exit code assert res.returncode == 0, res.stdout + res.stderr # 2. Stable row fields (checked BEFORE warning assertion so base b9c828f fails only at step 3) rows = _read_rows(mam_sandbox) assert session_name in rows row = rows[session_name] pre_assigned = agent in ("claude", "grok") expected_cmd_full = _norm_uuid( get_adapter(agent).spawn_spec( str(mock_agents / agent), "00000000-0000-0000-0000-000000000000" if pre_assigned else "", ) ) expected_source = "assigned" if pre_assigned else ("pending-discovery" if agent in ("opencode", "codex") else None) expected_verified = False if agent in ("claude", "grok", "opencode", "codex") else None expected_vis = "assigned (awaiting first message)" if pre_assigned else "unverified" expected_cp = { "workspace_root": os.path.realpath(mam_sandbox), "agent_sessions_yaml": os.path.realpath(mam_sandbox / ".mam" / "agent-sessions.yaml"), "mam_jobs_dir": os.path.realpath(mam_sandbox / ".mam" / "jobs"), "delegate_job_logs_dir": os.path.realpath(mam_sandbox / ".mam" / "delegate_job_logs"), } assert row["name"] == session_name assert row["status"] == "running" assert row["role"] == "creator" assert row["delegate_job_id"] is None assert row["herdr_server"] == ws_slug assert row["herdr_workspace"] == ws_slug.removeprefix("mam-") assert row["pane"]["cmd"] == agent assert _norm_uuid(row["pane"]["cmd_full"]) == expected_cmd_full assert os.path.realpath(row["pane"]["cwd"]) == os.path.realpath(ws) assert row.get("session_id_source") == expected_source assert row.get("session_id_verified") == expected_verified assert row["last_visible_status"] == expected_vis assert row["control_plane"] == expected_cp # 3. Warning line count warns = _warn_lines(res.stderr) assert len(warns) == EXPECTED_POST_DISCOVERED[agent], res.stderr if warns: assert "Issue #63" in warns[0] and agent in warns[0] def test_no_onboard_warning_precedes_a_later_refusal(mam_sandbox, mock_herdr, mock_agents): ws = mam_sandbox / "c68-ws" ws.mkdir(parents=True, exist_ok=True) epoch_a = int(time.time()) - 100 name_a, name_b = "c68-ws-creator-agy", "c68-ws-reviewer-agy" create_sh = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh" holder = subprocess.Popen(["sleep", "120"]) try: _write_rows(mam_sandbox, [_row(name_a, "agy", ws, epoch_a, pid=holder.pid)]) _mock_herdr_agent(mock_herdr, name_a, ws, holder.pid, epoch_a, pane_id="w1:p_a") env0 = dict(os.environ, MAM_ID_ORDER_WAIT_S="0") res = subprocess.run( ["bash", str(create_sh), "--workspace", str(ws), "--agent", "agy", "--role", "reviewer", "--session", name_b, "--no-onboard"], capture_output=True, text=True, cwd=str(mam_sandbox), env=env0, timeout=120, ) assert res.returncode == 1, res.stdout + res.stderr warns = _warn_lines(res.stderr) assert len(warns) == 1, res.stderr assert "Issue #63" in warns[0] assert name_a in res.stderr assert res.stderr.index("WARNING: --no-onboard:") < res.stderr.index(name_a), res.stderr finally: holder.kill() holder.wait()