"""Commands support for Pi tmux orchestration."""

from __future__ import annotations

import argparse
import json
import os
import shlex
import shutil
import stat
from pathlib import Path
from typing import Any

from . import runtime
from .budgeting import (
    budget_config_path,
    load_budget_config,
    packaged_budget_policy,
)
from .broker_client import broker_control_request
from .broker_store import (
    broker_paths,
    broker_role_generation,
    public_broker_snapshot,
    try_public_broker_snapshot,
    worker_guardrail_policy,
    worker_context_mode,
)
from .configuration import (
    effective_model_config,
    load_model_config,
    model_config_path,
    project_model_config,
    public_project_config,
    retained_orchestration_config,
    retained_project_config,
)
from .constants import (
    DEFAULT_IMPLEMENTATION_FLOW,
    DEFAULT_MODELS,
    MAX_JSON_ITEMS,
    RPC_TRANSPORT,
)
from .models import CommandResult, OrchestrationError
from .output import bounded_message, human_print, public_role
from .planning import retained_planning
from .profiles import (
    public_execution_profile,
    resolve_execution_profile,
    retained_execution_profile,
)
from .rpc import (
    load_rpc_events,
    load_rpc_registry,
    load_rpc_state,
    mark_rpc_registry_stopped,
    public_rpc_event,
    public_rpc_registry,
    public_rpc_state,
    rpc_control_request,
    rpc_role_paths,
    run_rpc_agent,
    unlink_private_regular,
)
from .supervisor_api import rpc_event_page, resolve_supervisor_target
from .storage import (
    absolute_path,
    ensure_private_directory,
    load_manifest,
    manifest_transport,
    require_regular_file,
    retained_coordination,
    save_manifest,
    validate_coordination_directory,
)
from .tmux import (
    attach_session,
    command_path,
    exact_session_target,
    exact_window_target,
    list_tmux_sessions,
    model_available,
    orchestrated_sessions,
    read_text_argument,
    resolve_session,
    run,
    tmux,
    validate_model,
    validate_session_name,
)
from .worker_resources import (
    prepare_worker_resources,
    revalidate_worker_resources,
    worker_tool_argument,
)

# Compatibility exports for callers that imported start surfaces from this module.
from .start_commands import (  # noqa: F401
    create_tmux_grid,
    construct_start_manifest,
    role_config,
    rollback_partial_start,
    session_exists,
    start_command,
    wait_for_custom_startup,
)


def orchestration_dashboard_summary(
    coord: Path, manifest: dict[str, Any]
) -> dict[str, Any]:
    if manifest.get("version", 0) < 3:
        return {"available": False, "reason": "legacy-run"}
    try:
        snapshot = public_broker_snapshot(coord)
    except Exception:
        # This optional read projection must never make list/control unavailable.
        return {"available": False, "reason": "temporarily-unavailable"}
    roles = snapshot["roles"]
    costs = [role["cost_total"] for role in roles]
    cost_total = (
        sum(costs)
        if costs and all(type(value) in {int, float} for value in costs)
        else None
    )
    context_values = [
        role["context_percent"]
        for role in roles
        if type(role["context_percent"]) in {int, float}
    ]
    workflow = snapshot["workflow"]
    usage = snapshot["usage"]
    return {
        "available": True,
        "workflow": {
            "state": workflow["state"],
            "round": workflow["round"],
            "implementation_flow": workflow["implementation_flow"],
        },
        "usage": {
            "provider_calls": usage["provider_calls"],
            "operational_tokens": usage["total_tokens"],
            "cost_total": cost_total,
            "context_percent": max(context_values) if context_values else None,
            "actual_provider_usage_only": True,
        },
        "roles": {
            "total": len(roles),
            "connected": sum(role["connected"] is True for role in roles),
        },
    }


def list_command(_: argparse.Namespace) -> CommandResult:
    sessions = orchestrated_sessions()
    values: list[dict[str, Any]] = []
    if not sessions:
        human_print("No running pi-tmux-agents sessions.")
        return CommandResult(
            data={"sessions": values, "truncated": False, "total_sessions": 0}
        )
    selected_sessions = sessions[:MAX_JSON_ITEMS] if runtime.JSON_MODE else sessions
    for session, coord in selected_sessions:
        try:
            manifest = load_manifest(coord, expected_session=session)
            role_values = [
                public_role(role, config, manifest_transport(manifest))
                for role, config in manifest["roles"].items()
            ]
            values.append(
                {
                    "session": session,
                    "valid": True,
                    "project": manifest["project"],
                    "execution_profile": retained_execution_profile(manifest),
                    "project_config": retained_project_config(manifest),
                    "orchestration_config": retained_orchestration_config(manifest),
                    "planning": retained_planning(manifest),
                    "dashboard": orchestration_dashboard_summary(coord, manifest),
                    "roles": role_values,
                    "paths": {"coordination": str(coord)},
                }
            )
            roles = ",".join(manifest["roles"].keys())
            human_print(f"{session}\t{manifest['project']}\troles={roles}\t{coord}")
        except OrchestrationError as error:
            message = bounded_message(error)
            values.append(
                {
                    "session": session,
                    "valid": False,
                    "project": None,
                    "roles": [],
                    "paths": {"coordination": str(coord)},
                    "error": {"code": error.code, "message": message},
                }
            )
            human_print(f"{session}\tinvalid manifest: {message}")
    return CommandResult(
        data={
            "sessions": values,
            "truncated": runtime.JSON_MODE and len(sessions) > len(selected_sessions),
            "total_sessions": len(sessions),
        }
    )


def coordination_files(coord: Path) -> list[tuple[Path, os.stat_result]]:
    """List legacy 0.4.x report files for retained-run compatibility only."""
    coord = validate_coordination_directory(coord)
    patterns = (
        "*.started.md",
        "probe.md",
        "playwright-*.md",
        "django-review-*.md",
        "handoff-*.md",
        "review-*.md",
        "implementation-ready.md",
    )
    files: set[Path] = set()
    for pattern in patterns:
        files.update(coord.glob(pattern))
    metadata = [
        (path, require_regular_file(path, f"coordination file {path.name}"))
        for path in files
    ]
    return sorted(metadata, key=lambda item: (item[1].st_mtime, item[0].name))


def status_roles(coord: Path, manifest: dict[str, Any]) -> list[dict[str, Any]]:
    values: list[dict[str, Any]] = []
    transport = manifest_transport(manifest)
    broker_roles: dict[str, dict[str, Any]] = {}
    if manifest.get("version", 0) >= 3:
        snapshot = try_public_broker_snapshot(coord)
        if snapshot is not None:
            broker_roles = {value["role"]: value for value in snapshot["roles"]}
    for role, config in manifest["roles"].items():
        value = public_role(role, config, transport)
        if role in broker_roles:
            value["broker_state"] = broker_roles[role]
        if transport == RPC_TRANSPORT:
            value["rpc_state"] = public_rpc_state(load_rpc_state(coord, role))
            value["rpc_registry"] = public_rpc_registry(load_rpc_registry(coord, role))
        values.append(value)
    return values


def _usage_display(value: object, *, suffix: str = "") -> str:
    return f"{value}{suffix}" if value is not None else "unavailable"


def _status_assignment_usage(role: dict[str, Any]) -> str | None:
    latest = role.get("latest_assignment_usage")
    if not isinstance(latest, dict):
        return None
    prefix = f"latest round={latest['round']} kind={latest['kind']}"
    usage = latest.get("usage")
    if not isinstance(usage, dict):
        return f"{prefix} usage=unavailable"
    return (
        f"{prefix} calls={usage['provider_calls']} input={usage['input_tokens']} "
        f"cache-read={usage['cache_read_tokens']} cache-write={usage['cache_write_tokens']} "
        f"output={usage['output_tokens']} "
        f"reasoning={_usage_display(usage['reasoning_tokens'])} "
        f"cost={_usage_display(usage['cost_total'])} "
        f"operational={usage['operational_tokens']} "
        f"context={_usage_display(usage['context_percent'], suffix='%')} "
        f"peak={_usage_display(usage['peak_context_tokens'])}"
    )


def status_command(args: argparse.Namespace) -> CommandResult:
    session, coord = resolve_session(args.session)
    manifest = load_manifest(coord, expected_session=session)
    human_print(f"Session: {session}")
    human_print(f"Project: {manifest['project']}")
    profile = retained_execution_profile(manifest)
    project_config = retained_project_config(manifest)
    orchestration_config = retained_orchestration_config(manifest)
    planning = retained_planning(manifest)
    human_print(
        "Execution profile: "
        + (
            f"{profile['name']} ({profile['kind']}, source={profile['source']})"
            if profile["name"] is not None
            else "unavailable (legacy run)"
        )
    )
    human_print(
        "Orchestration config: "
        + (
            f"{orchestration_config['path']} (schema v{orchestration_config['version']})"
            if orchestration_config["path"] is not None
            else "unavailable (legacy run)"
        )
    )
    human_print(
        "Project mapping: "
        + (
            f"matched {project_config['directory']}"
            if project_config["matched"] is True
            else (
                "none"
                if project_config["matched"] is False
                else "unavailable (legacy run)"
            )
        )
    )
    human_print(
        "Planning: "
        + (
            f"dynamic accepted via {planning['decision_model']['provider']}/"
            f"{planning['decision_model']['model']} "
            f"thinking={planning['decision_model']['thinking']} "
            f"source={planning['decision_model']['source']}"
            if planning["mode"] == "dynamic"
            else "static/manual or legacy"
        )
    )
    human_print(f"Coordination: {coord}")
    result = tmux(
        [
            "list-panes",
            "-t",
            exact_window_target(session, manifest["window"]),
            "-F",
            "#{pane_index}\t#{pane_id}\t#{pane_pid}\t#{pane_current_command}\t"
            "#{pane_dead}\t#{pane_title}",
        ],
        capture=True,
    )
    panes: list[dict[str, Any]] = []
    human_print("Panes:")
    for line in result.stdout.splitlines():
        columns = line.split("\t", 5)
        if len(columns) != 6:
            raise OrchestrationError(
                "tmux returned invalid pane metadata", "invalid_tmux_output"
            )
        index, pane_id, pid, current_command, dead, title = columns
        pane = {
            "index": int(index),
            "id": pane_id,
            "pid": int(pid),
            "command": bounded_message(current_command, 128),
            "dead": dead == "1",
            "title": bounded_message(title, 256),
        }
        if not runtime.JSON_MODE or len(panes) < MAX_JSON_ITEMS:
            panes.append(pane)
        human_print(
            f"  pane={index} id={pane_id} pid={pid} cmd={current_command} "
            f"dead={dead} title={title}"
        )
    broker_snapshot: dict[str, Any] | None = None
    files: list[tuple[Path, os.stat_result]] = []
    file_values: list[dict[str, Any]] = []
    if manifest.get("version", 0) >= 3:
        broker_snapshot = try_public_broker_snapshot(coord)
        if broker_snapshot is None:
            human_print("Workflow: temporarily unavailable")
        else:
            workflow = broker_snapshot["workflow"]
            usage = broker_snapshot["usage"]
            total_warning = (
                " budget=warning" if usage["soft_total_budget_exceeded"] else ""
            )
            human_print(
                f"Workflow: {workflow['state']} round={workflow['round']} "
                f"flow={workflow.get('implementation_flow', DEFAULT_IMPLEMENTATION_FLOW)} "
                f"forced={','.join(workflow.get('forced_specialists', [])) or 'none'} "
                f"tokens={usage['total_tokens']}{total_warning}"
            )
            context_overrides = workflow["worker_context_policy"]["overrides"]
            human_print(
                f"  worker context: default=prune; overrides={json.dumps(context_overrides, sort_keys=True)}"
            )
            continuation = workflow.get("continuation", {})
            if continuation.get("max_repair_rounds") is not None:
                human_print(
                    f"  repair rounds: {continuation['repair_rounds_admitted']}/{continuation['max_repair_rounds']}"
                )
            if continuation.get("pending_repair_round") is not None:
                human_print(
                    f"  Paused: repair_round_limit before round {continuation['pending_repair_round']}; "
                    f"incomplete, not approved. To authorize exactly one more round: "
                    f"pi-tmux-agents continue {session} --yes --command-id <32-hex-id>"
                )
            for activation in broker_snapshot.get("specialist_activations", []):
                human_print(
                    f"  activation {activation['role']}: {activation['decision']} "
                    f"rule={activation['rule_id']} source={activation['source']}"
                )
            for worker in broker_snapshot["roles"]:
                human_print(
                    f"  {worker['role']}: {worker['state']} connected={worker['connected']} "
                    f"tokens={worker['total_tokens']}"
                    f"{' budget=warning' if worker['soft_budget_exceeded'] else ''}"
                )
                latest_usage = _status_assignment_usage(worker)
                if latest_usage is not None:
                    human_print(f"    {latest_usage}")
                for guardrail in worker.get("assignment_guardrails", []):
                    human_print(
                        f"    guardrail={guardrail['level']} metric={guardrail['metric']} "
                        f"observed={guardrail['observed']} threshold={guardrail['threshold']}"
                    )
    else:
        human_print("Legacy coordination files:")
        files = coordination_files(coord)
        selected_files = files[:MAX_JSON_ITEMS] if runtime.JSON_MODE else files
        file_values = [
            {"name": path.name, "size_bytes": metadata.st_size}
            for path, metadata in selected_files
        ]
        if not files:
            human_print("  waiting for legacy agent status")
        for path, metadata in files:
            human_print(f"  {path.name}: {metadata.st_size} bytes")
    role_values = status_roles(coord, manifest)
    for role_value in role_values:
        if "specialist_contract" in role_value:
            human_print(
                f"  {role_value['name']}: contract={role_value['specialist_contract']} "
                f"thinking-source={role_value['thinking_source']} "
                f"activation={role_value['activation_source']} "
                "tools=custom-read-only-no-shell resources=not_checked"
            )
    if manifest_transport(manifest) == RPC_TRANSPORT:
        human_print("RPC workers:")
        for role_value in role_values:
            rpc_state = role_value.get("rpc_state")
            if rpc_state is None:
                human_print(f"  {role_value['name']}: starting/unavailable")
            else:
                registry = role_value.get("rpc_registry")
                registry_suffix = (
                    f" generation={registry['generation']} event={registry['last_event_sequence']}"
                    if registry is not None
                    else " registry=unavailable"
                )
                human_print(
                    f"  {role_value['name']}: {rpc_state['status']} "
                    f"streaming={rpc_state['is_streaming']} "
                    f"queue={rpc_state['steering_count']}+{rpc_state['follow_up_count']}"
                    f"{registry_suffix}"
                )
    paths = {"coordination": str(coord)}
    if manifest.get("version", 0) >= 3:
        paths["observer_socket"] = str(broker_paths(coord)["socket"])
    return CommandResult(
        data={
            "session": session,
            "project": manifest["project"],
            "execution_profile": profile,
            "project_config": project_config,
            "orchestration_config": orchestration_config,
            "planning": planning,
            "paths": paths,
            "roles": role_values,
            "panes": panes,
            "broker": broker_snapshot,
            "files": file_values,
            "truncated": {
                "panes": runtime.JSON_MODE
                and len(result.stdout.splitlines()) > len(panes),
                "files": False,
            },
        }
    )


def events_command(args: argparse.Namespace) -> CommandResult:
    session = validate_session_name(args.session)
    coord = retained_coordination(session, getattr(args, "run", None))
    manifest = load_manifest(coord, expected_session=session)
    if manifest_transport(manifest) != RPC_TRANSPORT:
        raise OrchestrationError("events require an orchestration using RPC workers")
    if args.role not in manifest["roles"]:
        available = ", ".join(manifest["roles"])
        raise OrchestrationError(
            f"Role {args.role!r} is not in {session}; available: {available}"
        )
    events = load_rpc_events(coord, args.role)
    after = args.after
    selected, cursor = rpc_event_page(events, after=after, limit=args.limit)
    gap = cursor["gap"]
    registry = load_rpc_registry(coord, args.role)
    human_print(
        f"Events: {session}/{args.role} run={coord.name} "
        f"after={after} returned={len(selected)} latest={cursor['latest']}"
    )
    if gap:
        human_print("Warning: requested cursor predates the retained journal window")
    for event in selected:
        human_print(
            f"  {event['sequence']} {event['timestamp']} {event['event']} "
            f"status={event['status']} command={event['command_id'] or '-'}"
        )
    return CommandResult(
        data={
            "session": session,
            "role": args.role,
            "run_id": coord.name,
            "paths": {"coordination": str(coord)},
            "registry": public_rpc_registry(registry),
            "events": [public_rpc_event(event) for event in selected],
            "cursor": cursor,
        }
    )


def attach_command(args: argparse.Namespace) -> CommandResult:
    json_output = getattr(args, "json_output", False)
    inside_tmux = bool(os.environ.get("TMUX"))
    if json_output and not inside_tmux:
        raise OrchestrationError(
            "Parent attach requires Pi to be running inside tmux",
            "interactive_only",
        )
    session, coord = resolve_session(args.session)
    manifest = load_manifest(coord, expected_session=session)
    attach_session(session)
    if inside_tmux:
        tmux(
            [
                "display-message",
                "-d",
                "5000",
                f"Attached to {session} · prefix then L detaches back without stopping workers",
            ],
            check=False,
        )
    return CommandResult(
        data={
            "session": session,
            "project": manifest["project"],
            "transport": manifest_transport(manifest),
            "mode": "switch-client" if inside_tmux else "attach-client",
            "return_hint": (
                "Press the tmux prefix, then L, to detach back to the invoking Pi without stopping workers."
                if inside_tmux
                else None
            ),
        }
    )


def send_keys(pane_id: str, message: str) -> None:
    """Legacy 0.4.x retained-run transport; v0.5.0 runs never call this."""
    tmux(["send-keys", "-t", pane_id, "-l", "--", message])
    tmux(["send-keys", "-t", pane_id, "Enter"])


def control_target(args: argparse.Namespace) -> tuple[str, Path, dict[str, Any]]:
    run_id = getattr(args, "run", None)
    if run_id is not None:
        coord, manifest = resolve_supervisor_target(
            args.session, run_id, require_rpc=True
        )
        return manifest["session"], coord, manifest
    session, coord = resolve_session(args.session)
    return session, coord, load_manifest(coord, expected_session=session)


def send_command(args: argparse.Namespace) -> CommandResult:
    session, coord, manifest = control_target(args)
    if args.role not in manifest["roles"]:
        available = ", ".join(manifest["roles"].keys())
        raise OrchestrationError(
            f"Role {args.role!r} is not in {session}; available: {available}"
        )
    message = read_text_argument(args.message, args.message_file, "message").strip()
    transport = manifest_transport(manifest)
    acknowledgement: dict[str, Any] | None = None
    if manifest.get("version", 0) >= 3:
        if getattr(args, "run", None) is not None:
            _session, live_coord = resolve_session(args.session)
            if live_coord != coord:
                raise OrchestrationError(
                    "Broker control requires the exact run to be hosted by the live tmux session",
                    "broker_not_live",
                )
        acknowledgement = broker_control_request(
            coord,
            args.role,
            "send",
            message=message,
            delivery=args.delivery,
            command_id=getattr(args, "command_id", None),
        )
        acknowledged = True
    elif transport == RPC_TRANSPORT:
        acknowledgement = rpc_control_request(
            coord,
            manifest,
            args.role,
            "prompt",
            message=message,
            delivery=args.delivery,
            command_id=getattr(args, "command_id", None),
        )
        acknowledged = True
    else:
        if args.delivery != "steer":
            raise OrchestrationError("follow-up delivery requires RPC workers")
        if getattr(args, "command_id", None) is not None:
            raise OrchestrationError("command IDs require RPC workers")
        # Retained 0.4.x runs remain operable. Manifest v3 runs are rejected above.
        send_keys(manifest["roles"][args.role]["pane_id"], message)
        acknowledged = False
    suffix = (
        f" (status={acknowledgement['status']} id={acknowledgement['id']})"
        if acknowledgement is not None
        else ""
    )
    human_print(f"Sent message to {session}/{args.role} via {transport}{suffix}")
    return CommandResult(
        data={
            "session": session,
            "run_id": coord.name,
            "role": args.role,
            "sent": True,
            "transport": transport,
            "delivery": args.delivery,
            "acknowledged": acknowledged,
            "command_id": acknowledgement["id"] if acknowledgement else None,
            "command_status": acknowledgement["status"] if acknowledgement else None,
            "duplicate": acknowledgement["duplicate"] if acknowledgement else False,
            "event_sequence": (
                acknowledgement.get("event_sequence") if acknowledgement else None
            ),
        }
    )


def continue_command(args: argparse.Namespace) -> CommandResult:
    if not args.yes:
        raise OrchestrationError(
            "continue authorizes one additional repair round; pass --yes"
        )
    session, coord, manifest = control_target(args)
    if manifest.get("version", 0) < 3:
        raise OrchestrationError("continue requires a brokered orchestration")
    acknowledgement = broker_control_request(
        coord,
        "implementer",
        "continue",
        command_id=args.command_id,
    )
    human_print(
        f"One-round continuation acknowledged by {session}; not workflow completion"
    )
    return CommandResult(
        data={
            "session": session,
            "run_id": coord.name,
            "role": "implementer",
            "acknowledged": True,
            "command_id": acknowledgement["id"],
            "command_status": acknowledgement["status"],
            "duplicate": acknowledgement["duplicate"],
        }
    )


def abort_command(args: argparse.Namespace) -> CommandResult:
    session, coord, manifest = control_target(args)
    if args.role not in manifest["roles"]:
        available = ", ".join(manifest["roles"].keys())
        raise OrchestrationError(
            f"Role {args.role!r} is not in {session}; available: {available}"
        )
    if manifest.get("version", 0) >= 3:
        if getattr(args, "run", None) is not None:
            _session, live_coord = resolve_session(args.session)
            if live_coord != coord:
                raise OrchestrationError(
                    "Broker control requires the exact run to be hosted by the live tmux session",
                    "broker_not_live",
                )
        acknowledgement = broker_control_request(
            coord,
            args.role,
            "abort",
            command_id=getattr(args, "command_id", None),
        )
    else:
        if manifest_transport(manifest) != RPC_TRANSPORT:
            raise OrchestrationError(
                "abort requires a brokered or RPC-worker orchestration"
            )
        acknowledgement = rpc_control_request(
            coord,
            manifest,
            args.role,
            "abort",
            command_id=getattr(args, "command_id", None),
        )
    human_print(
        f"Abort acknowledged by {session}/{args.role} "
        f"(status={acknowledgement['status']} id={acknowledgement['id']})"
    )
    return CommandResult(
        data={
            "session": session,
            "run_id": coord.name,
            "role": args.role,
            "aborted": True,
            "transport": manifest_transport(manifest),
            "acknowledged": True,
            "command_id": acknowledgement["id"],
            "command_status": acknowledgement["status"],
            "duplicate": acknowledgement["duplicate"],
            "event_sequence": acknowledgement.get("event_sequence"),
        }
    )


def restart_command(args: argparse.Namespace) -> CommandResult:
    if not args.yes:
        raise OrchestrationError(
            "restart respawns the role's worker process and preserves its brokered "
            "Pi conversation and JSONL history; pass --yes"
        )
    session, coord = resolve_session(args.session)
    manifest = load_manifest(coord, expected_session=session)
    if args.role not in manifest["roles"]:
        available = ", ".join(manifest["roles"].keys())
        raise OrchestrationError(
            f"Role {args.role!r} is not in {session}; available: {available}"
        )
    role = manifest["roles"][args.role]
    revalidate_worker_resources(manifest, args.role)
    if args.provider:
        role["provider"] = args.provider
    if args.model:
        role["model"] = args.model
    if args.thinking:
        role["thinking"] = args.thinking
    if not args.skip_model_check:
        validate_model(args.role, role)
    save_manifest(coord, manifest)
    broker_handover_prepared = manifest.get("version", 0) >= 3
    if broker_handover_prepared:
        broker_control_request(coord, args.role, "restart")
    try:
        if manifest_transport(manifest) == RPC_TRANSPORT:
            rpc_paths = rpc_role_paths(coord, args.role, create=True)
            unlink_private_regular(rpc_paths["state"], f"{args.role} RPC state")
        command = shlex.join(
            [
                str(runtime.SCRIPT_PATH),
                "_run-agent",
                "--state-root",
                str(coord.parent.parent),
                "--coord",
                str(coord),
                "--role",
                args.role,
            ]
        )
        tmux(["respawn-pane", "-k", "-t", role["pane_id"], command])
    except Exception:
        if broker_handover_prepared:
            try:
                broker_control_request(coord, args.role, "restart_failed")
            except OrchestrationError as error:
                raise OrchestrationError(
                    "role respawn failed after broker restart preparation; "
                    "handover failure acknowledgement is uncertain",
                    "broker_uncertain",
                ) from error
        raise
    human_print(
        f"Restarted {session}/{args.role} with "
        f"{role['provider']}/{role['model']} thinking={role['thinking']}"
    )
    return CommandResult(
        data={
            "session": session,
            "role": public_role(args.role, role, manifest_transport(manifest)),
            "restarted": True,
        }
    )


def stop_command(args: argparse.Namespace) -> CommandResult:
    if not args.yes:
        raise OrchestrationError("stop kills the selected tmux agent grid; pass --yes")
    session, coord = resolve_session(args.session)
    manifest = load_manifest(coord, expected_session=session)
    tmux(["kill-session", "-t", exact_session_target(session)])
    if manifest.get("version") in {3, 4}:
        socket_path = broker_paths(coord)["socket"]
        try:
            metadata = socket_path.lstat()
        except FileNotFoundError:
            pass
        else:
            if stat.S_ISSOCK(metadata.st_mode):
                socket_path.unlink()
    registry_finalization_failures: list[str] = []
    if manifest_transport(manifest) == RPC_TRANSPORT:
        for role in manifest["roles"]:
            try:
                mark_rpc_registry_stopped(coord, role)
            except OrchestrationError:
                registry_finalization_failures.append(role)
    human_print(f"Stopped {session}")
    if registry_finalization_failures:
        human_print(
            "Warning: retained RPC registry finalization failed for "
            + ", ".join(registry_finalization_failures)
        )
    human_print(f"Coordination state retained at {coord}")
    return CommandResult(
        data={
            "session": session,
            "stopped": True,
            "state_retained": True,
            "paths": {"coordination": str(coord)},
            "registry_finalization": {
                "failed_roles": registry_finalization_failures,
            },
        }
    )


def doctor_command(args: argparse.Namespace) -> CommandResult:
    try:
        project = Path(args.project).expanduser().resolve(strict=True)
    except OSError as error:
        raise OrchestrationError(
            f"Project directory does not exist: {args.project}"
        ) from error
    if not project.is_dir():
        raise OrchestrationError(f"Project directory does not exist: {project}")
    configured_models = load_model_config(project=project)
    config_path = model_config_path(project)
    matched_project = project_model_config(configured_models, project)
    project_metadata = public_project_config(matched_project)
    config_in_use = bool(
        configured_models["default_profile"]
        or configured_models["profiles"]
        or configured_models["defaults"]
        or any(configured_models["roles"].values())
        or configured_models["projects"]
        or configured_models.get("planner") is not None
    )
    execution_profile = resolve_execution_profile(
        configured_models, project=matched_project
    )
    configured_budget = load_budget_config()
    budget_path = budget_config_path()
    budget_in_use = configured_budget != packaged_budget_policy()
    budget_data = {
        "config_path": str(budget_path),
        "configured": budget_in_use,
        "effective": configured_budget,
    }
    ok = True
    command_checks: list[dict[str, Any]] = []
    for name in ("pi", "tmux", "python3"):
        path = shutil.which(name)
        command_checks.append(
            {"name": name, "status": "ok" if path else "fail", "path": path}
        )
        if path:
            human_print(f"OK   {name}: {path}")
        else:
            human_print(f"FAIL {name}: not found")
            ok = False
    if not ok:
        return CommandResult(
            data={
                "commands": command_checks,
                "tmux": None,
                "model_checks": [],
                "model_policy": {
                    "config_path": str(config_path),
                    "configured": config_in_use,
                    "execution_profile": public_execution_profile(execution_profile),
                    "project_config": project_metadata,
                },
                "budget_policy": budget_data,
                "paths": {
                    "state_root": str(absolute_path(runtime.STATE_ROOT)),
                    "model_config": str(config_path),
                    "budget_config": str(budget_path),
                    "project": str(project),
                },
            },
            code=1,
            error_code="missing_prerequisite",
            error_message="One or more required local commands are unavailable",
        )

    version = bounded_message(
        run([command_path("tmux"), "-V"], capture=True).stdout, 128
    )
    human_print(f"OK   {version}")
    tmux_data: dict[str, Any] = {
        "version": version,
        "server_running": bool(list_tmux_sessions()),
        "extended_keys": None,
        "extended_keys_format": None,
    }
    if tmux_data["server_running"]:
        extended = tmux(
            ["show-options", "-gv", "extended-keys"], check=False, capture=True
        )
        key_format = tmux(
            ["show-options", "-gv", "extended-keys-format"],
            check=False,
            capture=True,
        )
        extended_value = (
            bounded_message(extended.stdout, 64)
            if extended.returncode == 0
            else "unknown"
        )
        format_value = (
            bounded_message(key_format.stdout, 64)
            if key_format.returncode == 0
            else "unknown"
        )
        label = "OK" if extended_value == "on" else "WARN"
        human_print(f"{label:<4} tmux extended-keys: {extended_value}")
        label = "OK" if format_value == "csi-u" else "WARN"
        human_print(f"{label:<4} tmux extended-keys-format: {format_value}")
        tmux_data["extended_keys"] = extended_value
        tmux_data["extended_keys_format"] = format_value
    else:
        human_print(
            "INFO tmux server is not running; extended-key options were not inspected"
        )

    model_checks: list[dict[str, Any]] = []
    model_catalogs = {}
    for role in DEFAULT_MODELS:
        config = effective_model_config(
            role, configured_models, execution_profile, matched_project
        )
        available, detail = model_available(
            config["provider"], config["model"], model_catalogs
        )
        label = "OK" if available else "WARN"
        human_print(
            f"{label:<4} {role}: {config['provider']}/{config['model']} ({detail})"
        )
        model_checks.append(
            {
                "role": role,
                "provider": config["provider"],
                "model": config["model"],
                "available": available,
                "detail": bounded_message(detail, 256),
            }
        )
    human_print(
        f"OK   model config: {config_path} ({'configured' if config_in_use else 'packaged defaults'})"
    )
    human_print(
        f"OK   execution profile: {execution_profile['name']} "
        f"({execution_profile['kind']}, source={execution_profile['source']})"
    )
    human_print(
        "OK   project mapping: "
        + (
            f"matched {project_metadata['directory']}"
            if project_metadata["matched"]
            else f"none for {project}"
        )
    )
    human_print(
        f"OK   budget config: {budget_path} "
        f"({'configured' if budget_in_use else 'packaged defaults'}; "
        f"mode={configured_budget['enforcement']}, observational=true)"
    )
    human_print(f"OK   state root: {runtime.STATE_ROOT}")
    return CommandResult(
        data={
            "commands": command_checks,
            "tmux": tmux_data,
            "model_checks": model_checks,
            "model_policy": {
                "config_path": str(config_path),
                "configured": config_in_use,
                "execution_profile": public_execution_profile(execution_profile),
                "project_config": project_metadata,
            },
            "budget_policy": budget_data,
            "paths": {
                "state_root": str(absolute_path(runtime.STATE_ROOT)),
                "model_config": str(config_path),
                "budget_config": str(budget_path),
                "project": str(project),
            },
        }
    )


def run_agent_command(args: argparse.Namespace) -> int:
    runtime.STATE_ROOT = Path(args.state_root)
    coord = absolute_path(Path(args.coord))
    manifest = load_manifest(coord)
    role = manifest["roles"].get(args.role)
    if role is None:
        raise OrchestrationError(f"Unknown role in manifest: {args.role}")
    if manifest.get("version", 0) >= 3:
        os.environ["PI_TMUX_ORCHESTRATOR_GENERATION"] = str(
            broker_role_generation(coord, args.role)
        )
    if manifest_transport(manifest) == RPC_TRANSPORT:
        return run_rpc_agent(coord, manifest, args.role, role)
    project = manifest["project"]
    ensure_private_directory(Path(role["session_dir"]), parents=True)
    if manifest.get("version", 0) < 3:
        prompt_path = Path(role["prompt_path"])
        require_regular_file(prompt_path, "role prompt", nonempty=True)
    command = [
        command_path("pi"),
        "--session-dir",
        role["session_dir"],
        "--name",
        f"{Path(project).name} {args.role}",
    ]
    if manifest.get("version", 0) >= 3:
        command.extend(["--session-id", role["session_id"]])
    command.extend(
        [
            "--provider",
            role["provider"],
            "--model",
            role["model"],
            "--thinking",
            role["thinking"],
        ]
    )
    if manifest["approve_project"]:
        command.append("--approve")
    tools = worker_tool_argument(args.role, role, manifest.get("version", 0) >= 3)
    if tools:
        command.extend(["--tools", tools])
    specialist_contract = None
    if manifest.get("version", 0) >= 3:
        token_path = coord / f"{args.role}.token"
        require_regular_file(token_path, "worker broker token", nonempty=True)
        token = token_path.read_text(encoding="utf-8").strip()
        specialist_contract = prepare_worker_resources(
            command, coord, manifest, args.role, runtime.WORKER_EXTENSION_PATH
        )
    else:
        command.extend(
            [
                f"@{prompt_path}",
                "Follow the attached role instructions and begin.",
            ]
        )
    environment = os.environ.copy()
    environment.pop("PI_TMUX_CONTROLLER", None)
    environment.pop("PI_TMUX_CONTROLLER_HOME", None)
    environment.pop("PI_TMUX_ORCHESTRATOR_SPECIALIST_CONTRACT", None)
    environment.pop("TYPESAFE_API_KEY", None)
    if specialist_contract is not None:
        environment["PI_TMUX_ORCHESTRATOR_SPECIALIST_CONTRACT"] = specialist_contract
    environment["PI_SKIP_VERSION_CHECK"] = "1"
    environment["PI_TELEMETRY"] = "0"
    if manifest.get("version", 0) >= 3:
        guardrails = worker_guardrail_policy(coord)
        environment["PI_TMUX_ORCHESTRATOR_CONTEXT_MODE"] = worker_context_mode(
            coord, args.role
        )
        environment["PI_TMUX_ORCHESTRATOR_ROLE"] = args.role
        environment["PI_TMUX_ORCHESTRATOR_TOKEN"] = token
        environment["PI_TMUX_ORCHESTRATOR_SOCKET"] = str(broker_paths(coord)["socket"])
        environment["PI_TMUX_ORCHESTRATOR_GUARDRAILS"] = json.dumps(
            guardrails, separators=(",", ":"), sort_keys=True
        )
    os.chdir(project)
    os.execvpe(command[0], command, environment)
    return 0
