import argparse
import html
import json
import os
import queue
import shlex
import socket
import subprocess
import sys
import threading
import time
import uuid
import webbrowser
from contextlib import contextmanager
from importlib import metadata as importlib_metadata
from pathlib import Path
from urllib.parse import quote_plus
import urllib.error
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

from hiveos.intelligence.tracing import build_trace_event, emit_trace, read_trace_events
from hiveos.intelligence import tracing as tracing_module
from hiveos import config as hive_config

TRACE_BUNDLE_VERSION = 1


def _now_ms():
    return int(time.time() * 1000)


def _shorten(value, limit=800):
    text = str(value or "")
    if len(text) <= limit:
        return text
    return text[: max(0, limit - 3)] + "..."


def _build_run_id(prefix="observe"):
    return f"{prefix}:{uuid.uuid4().hex[:12]}"


def _hive_version():
    for name in ("hiveos-trace", "hiveos"):
        try:
            return str(importlib_metadata.version(name))
        except importlib_metadata.PackageNotFoundError:
            continue
    return "dev"


def _active_trace_log_path():
    # Resolve to an absolute path so `trace show` works even when cwd changes.
    return str(Path(str(tracing_module.TRACE_LOG_PATH or "trace_events.log")).expanduser().resolve())


def _trace_home():
    configured = str(os.environ.get("HIVE_TRACE_HOME") or "").strip()
    if configured:
        return Path(configured)
    return Path.home() / ".hiveos-trace"


def _runs_dir():
    path = _trace_home() / "runs"
    path.mkdir(parents=True, exist_ok=True)
    return path


def _run_file(run_id):
    safe = str(run_id or "").replace(":", "_")
    return _runs_dir() / f"{safe}.json"


def _persist_run_record(record):
    path = _run_file(record.get("run_id"))
    path.write_text(json.dumps(record, indent=2), encoding="utf-8")
    return path


def _touch_run_record(run_id, **fields):
    run_key = str(run_id or "").strip()
    if not run_key:
        return None
    record = _read_run_record(run_key)
    if not record:
        return None
    updates = dict(fields or {})
    updates["last_update_ms"] = _now_ms()
    record.update(updates)
    _persist_run_record(record)
    return record


def _read_run_record(run_id):
    path = _run_file(run_id)
    if not path.exists():
        return None
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return None


def _list_run_records(limit=100):
    records = []
    for path in _runs_dir().glob("*.json"):
        try:
            record = json.loads(path.read_text(encoding="utf-8"))
            if isinstance(record, dict):
                records.append(record)
        except Exception:
            continue
    records.sort(key=lambda item: int(item.get("started_at_ms") or 0), reverse=True)
    return records[: max(1, min(1000, int(limit or 100)))]


def _list_all_run_records():
    records = []
    for path in _runs_dir().glob("*.json"):
        try:
            record = json.loads(path.read_text(encoding="utf-8"))
            if isinstance(record, dict):
                records.append(record)
        except Exception:
            continue
    records.sort(key=lambda item: int(item.get("started_at_ms") or 0), reverse=True)
    return records


def _record_is_archived(record):
    return bool(record.get("archived"))


def _record_effective_status(record):
    if _record_is_archived(record):
        return "archived"
    return str(record.get("status") or "unknown")


def _filter_records_by_status(records, status):
    wanted = str(status or "all").strip().lower()
    if wanted in {"", "all"}:
        return list(records)
    if wanted == "active":
        return [record for record in records if not _record_is_archived(record)]
    if wanted == "archived":
        return [record for record in records if _record_is_archived(record)]
    return [
        record
        for record in records
        if (not _record_is_archived(record)) and str(record.get("status") or "unknown").lower() == wanted
    ]


def _record_reference_time_ms(record):
    if _record_is_archived(record):
        return int(record.get("archived_at_ms") or record.get("finished_at_ms") or record.get("started_at_ms") or 0)
    return int(record.get("finished_at_ms") or record.get("started_at_ms") or 0)


def _parse_age_window_ms(value):
    text = str(value or "").strip().lower()
    if not text:
        raise ValueError("older-than must be provided")
    scale = 24 * 60 * 60 * 1000
    if text[-1] in {"s", "m", "h", "d"}:
        unit = text[-1]
        num = text[:-1].strip()
        if not num or not num.isdigit():
            raise ValueError("older-than must be numeric with optional suffix s|m|h|d")
        base = int(num)
        if unit == "s":
            scale = 1000
        elif unit == "m":
            scale = 60 * 1000
        elif unit == "h":
            scale = 60 * 60 * 1000
        else:
            scale = 24 * 60 * 60 * 1000
        return max(1000, base * scale)
    if text.isdigit():
        return max(1000, int(text) * scale)
    raise ValueError("older-than must be numeric with optional suffix s|m|h|d")


def _parse_duration_window_ms(value, *, default_unit="s"):
    text = str(value or "").strip().lower()
    if not text:
        raise ValueError("duration window must be provided")
    unit = default_unit if default_unit in {"s", "m", "h", "d"} else "s"
    number = text
    if text[-1] in {"s", "m", "h", "d"}:
        unit = text[-1]
        number = text[:-1].strip()
    if not number or not number.isdigit():
        raise ValueError("duration window must be numeric with optional suffix s|m|h|d")
    base = int(number)
    scale = 1000
    if unit == "m":
        scale = 60 * 1000
    elif unit == "h":
        scale = 60 * 60 * 1000
    elif unit == "d":
        scale = 24 * 60 * 60 * 1000
    return max(1000, base * scale)


def _reconcile_stale_running_runs(*, stale_after_ms, now_ms=None, emit_events=True):
    threshold = max(1000, int(stale_after_ms or 0))
    now = int(now_ms or _now_ms())
    checked = 0
    reconciled = []
    for record in _list_all_run_records():
        if str(record.get("status") or "").strip().lower() != "running":
            continue
        checked += 1
        run_id = str(record.get("run_id") or "").strip()
        if not run_id:
            continue
        heartbeat = int(record.get("last_update_ms") or record.get("started_at_ms") or 0)
        if heartbeat <= 0:
            continue
        if now - heartbeat < threshold:
            continue
        started = int(record.get("started_at_ms") or heartbeat)
        record["status"] = "failed"
        record["exit_code"] = int(record.get("exit_code") or 1)
        record["finished_at_ms"] = now
        record["duration_ms"] = max(0, now - started)
        record["last_update_ms"] = now
        record["stale_reconciled"] = True
        record["stale_reconciled_at_ms"] = now
        record["stale_reconcile_reason"] = "orphaned_run_reconciled"
        _persist_run_record(record)
        reconciled.append(run_id)
        if emit_events:
            emit_observe_event(
                run_id=run_id,
                event_type="observe_run_reconciled",
                outcome="failed",
                payload={
                    "step_name": "run.reconcile",
                    "reason": "orphaned_run_reconciled",
                    "stale_after_ms": threshold,
                    "source": "cli",
                },
                agent_id="observer.reconcile",
            )
    return {
        "checked_running": checked,
        "reconciled_runs": len(reconciled),
        "reconciled_run_ids": reconciled,
        "stale_after_ms": threshold,
    }


def _maybe_auto_reconcile():
    if not bool(getattr(hive_config, "TRACE_RECONCILE_ENABLED", True)):
        return None
    if not bool(getattr(hive_config, "TRACE_RECONCILE_AUTO_ON_READ", True)):
        return None
    stale_sec = int(getattr(hive_config, "TRACE_RECONCILE_STALE_SEC", 300) or 300)
    return _reconcile_stale_running_runs(stale_after_ms=max(1, stale_sec) * 1000, emit_events=True)


def _archive_run_record(run_id, *, archive=True, reason="", actor="operator"):
    run_key = str(run_id or "").strip()
    if not run_key:
        raise ValueError("run_id is required")
    record = _read_run_record(run_key)
    if not record:
        return None
    now = _now_ms()
    if archive:
        record["archived"] = True
        record["archived_at_ms"] = now
        record["archive_reason"] = str(reason or "").strip()
        record["archive_actor"] = str(actor or "operator").strip() or "operator"
        record["last_update_ms"] = now
    else:
        record["archived"] = False
        record["archived_at_ms"] = None
        record["archive_reason"] = None
        record["archive_actor"] = None
        record["last_update_ms"] = now
    _persist_run_record(record)
    return record


def _drop_events_for_runs(run_ids_by_log_path):
    dropped = 0
    touched_files = 0
    for base_path, run_ids in run_ids_by_log_path.items():
        wanted = {str(run_id or "").strip() for run_id in (run_ids or set()) if str(run_id or "").strip()}
        if not wanted:
            continue
        for file_path in _iter_trace_files_from_base_oldest_first(base_path):
            try:
                source = Path(file_path)
                tmp = source.with_suffix(source.suffix + ".tmp")
                changed = False
                removed_here = 0
                with source.open("r", encoding="utf-8") as src, tmp.open("w", encoding="utf-8") as dst:
                    for line in src:
                        keep = True
                        raw = line.strip()
                        if raw:
                            try:
                                event = json.loads(raw)
                                if str(event.get("run_id") or "").strip() in wanted:
                                    keep = False
                            except Exception:
                                keep = True
                        if keep:
                            dst.write(line)
                        else:
                            changed = True
                            removed_here += 1
                if changed:
                    os.replace(str(tmp), str(source))
                    dropped += removed_here
                    touched_files += 1
                else:
                    tmp.unlink(missing_ok=True)
            except Exception:
                continue
    return {"dropped_events": dropped, "touched_files": touched_files}


def _iter_trace_files_from_base_oldest_first(base_path):
    text = str(base_path or "").strip()
    if not text:
        return []
    path = Path(text)
    parent = path.parent
    name = path.name
    rotated = []
    try:
        for candidate in parent.glob(f"{name}.*"):
            suffix = candidate.name[len(name) + 1 :]
            if suffix.isdigit():
                rotated.append((int(suffix), candidate))
    except Exception:
        rotated = []
    rotated.sort(key=lambda item: item[0], reverse=True)
    files = [candidate for _, candidate in rotated]
    files.append(path)
    return [item for item in files if item.exists()]


def _read_trace_events_for_run(run_id, *, limit=200, trace_log_path=""):
    from collections import deque

    rid = str(run_id or "").strip()
    if not rid:
        return []
    out = deque(maxlen=max(1, int(limit or 200)))
    candidates = []
    preferred = str(trace_log_path or "").strip()
    if preferred:
        candidates.extend(_iter_trace_files_from_base_oldest_first(preferred))
    active = _active_trace_log_path()
    if active and active != preferred:
        candidates.extend(_iter_trace_files_from_base_oldest_first(active))
    seen_paths = set()
    for path in candidates:
        norm = str(path)
        if norm in seen_paths:
            continue
        seen_paths.add(norm)
        try:
            with path.open("r", encoding="utf-8") as handle:
                for line in handle:
                    line = line.strip()
                    if not line:
                        continue
                    try:
                        event = json.loads(line)
                    except Exception:
                        continue
                    if str(event.get("run_id") or "") == rid:
                        out.append(event)
        except OSError:
            continue
    return list(out)


def _read_all_trace_events_for_run(run_id, *, trace_log_path=""):
    rid = str(run_id or "").strip()
    if not rid:
        return []
    out = []
    candidates = []
    preferred = str(trace_log_path or "").strip()
    if preferred:
        candidates.extend(_iter_trace_files_from_base_oldest_first(preferred))
    active = _active_trace_log_path()
    if active and active != preferred:
        candidates.extend(_iter_trace_files_from_base_oldest_first(active))
    seen_paths = set()
    for path in candidates:
        norm = str(path)
        if norm in seen_paths:
            continue
        seen_paths.add(norm)
        try:
            with path.open("r", encoding="utf-8") as handle:
                for line in handle:
                    line = line.strip()
                    if not line:
                        continue
                    try:
                        event = json.loads(line)
                    except Exception:
                        continue
                    if str(event.get("run_id") or "") == rid:
                        out.append(event)
        except OSError:
            continue
    return out


def emit_observe_event(
    *,
    run_id,
    event_type,
    outcome="success",
    payload=None,
    agent_id="observer.sdk",
    task_id=None,
    chunk_id=None,
    zone_id=None,
):
    _touch_run_record(run_id)
    event = build_trace_event(
        event_type=event_type,
        run_id=run_id,
        outcome=outcome if outcome in {"success", "failed", "denied"} else "success",
        payload=payload or {},
        agent_id=agent_id,
        task_id=task_id,
        chunk_id=chunk_id,
        zone_id=zone_id,
    )
    return emit_trace(event)


@contextmanager
def observe_run(
    run_name,
    *,
    run_id=None,
    metadata=None,
    agent_id="observer.sdk",
):
    rid = str(run_id or _build_run_id())
    started_at = _now_ms()
    emit_observe_event(
        run_id=rid,
        event_type="observe_run_started",
        outcome="success",
        payload={
            "run_name": str(run_name or "run"),
            "metadata": dict(metadata or {}),
            "source": "sdk",
        },
        agent_id=agent_id,
    )
    try:
        yield rid
        elapsed_ms = max(0, _now_ms() - started_at)
        emit_observe_event(
            run_id=rid,
            event_type="observe_run_finished",
            outcome="success",
            payload={"run_name": str(run_name or "run"), "duration_ms": elapsed_ms, "source": "sdk"},
            agent_id=agent_id,
        )
    except Exception as exc:
        elapsed_ms = max(0, _now_ms() - started_at)
        emit_observe_event(
            run_id=rid,
            event_type="observe_run_finished",
            outcome="failed",
            payload={
                "run_name": str(run_name or "run"),
                "duration_ms": elapsed_ms,
                "error": _shorten(str(exc), limit=400),
                "source": "sdk",
            },
            agent_id=agent_id,
        )
        raise


def observe_step(run_id, step_name, *, payload=None, outcome="success", agent_id="observer.sdk"):
    return emit_observe_event(
        run_id=run_id,
        event_type="observe_step",
        outcome=outcome,
        payload={"step_name": str(step_name or "step"), "source": "sdk", **dict(payload or {})},
        agent_id=agent_id,
    )


def observe_checkpoint(
    run_id,
    checkpoint_id,
    *,
    step_name="",
    state_ref="",
    payload=None,
    agent_id="observer.sdk",
):
    body = {
        "checkpoint_id": str(checkpoint_id or "").strip(),
        "step_name": str(step_name or "").strip(),
        "state_ref": str(state_ref or "").strip(),
        "source": "sdk",
    }
    body.update(dict(payload or {}))
    if not body["checkpoint_id"]:
        raise ValueError("checkpoint_id is required")
    return emit_observe_event(
        run_id=run_id,
        event_type="observe_checkpoint",
        outcome="success",
        payload=body,
        agent_id=agent_id,
    )


def observe_rerun_request(
    run_id,
    *,
    from_checkpoint_id,
    reason="",
    overrides=None,
    agent_id="observer.sdk",
):
    checkpoint_id = str(from_checkpoint_id or "").strip()
    if not checkpoint_id:
        raise ValueError("from_checkpoint_id is required")
    return emit_observe_event(
        run_id=run_id,
        event_type="observe_rerun_requested",
        outcome="success",
        payload={
            "from_checkpoint_id": checkpoint_id,
            "reason": str(reason or "").strip(),
            "overrides": dict(overrides or {}),
            "source": "sdk",
        },
        agent_id=agent_id,
    )


def _reader_thread(stream, label, out_queue):
    try:
        for line in iter(stream.readline, ""):
            out_queue.put((label, line))
    except Exception as exc:
        out_queue.put((label, f"[stream_decode_error] {_shorten(exc, limit=240)}\n"))
    finally:
        out_queue.put((label, None))


def _safe_stream_write(target, text):
    try:
        target.write(text)
        target.flush()
        return
    except UnicodeEncodeError:
        pass
    try:
        encoding = str(getattr(target, "encoding", "") or "utf-8")
        data = str(text or "").encode(encoding, errors="replace")
        buffer = getattr(target, "buffer", None)
        if buffer is not None:
            buffer.write(data)
            buffer.flush()
            return
    except Exception:
        pass
    try:
        target.write(str(text or "").encode("utf-8", errors="replace").decode("utf-8", errors="replace"))
        target.flush()
    except Exception:
        return


def _stream_process_output(proc, run_id, command_text, agent_id, heartbeat_cb=None):
    out_queue = queue.Queue()
    stdout_thread = threading.Thread(target=_reader_thread, args=(proc.stdout, "stdout", out_queue), daemon=True)
    stderr_thread = threading.Thread(target=_reader_thread, args=(proc.stderr, "stderr", out_queue), daemon=True)
    stdout_thread.start()
    stderr_thread.start()
    closed = set()
    seq = 0
    while len(closed) < 2:
        try:
            channel, chunk = out_queue.get(timeout=1.0)
        except queue.Empty:
            if heartbeat_cb is not None:
                try:
                    heartbeat_cb()
                except Exception:
                    pass
            continue
        if chunk is None:
            closed.add(channel)
            continue
        seq += 1
        if channel == "stdout":
            _safe_stream_write(sys.stdout, chunk)
        else:
            _safe_stream_write(sys.stderr, chunk)
        emit_observe_event(
            run_id=run_id,
            event_type="observe_output",
            outcome="success",
            payload={
                "step_name": "command.output",
                "command": command_text,
                "stream": channel,
                "seq": seq,
                "text": _shorten(chunk.rstrip("\n"), limit=2000),
                "source": "cli",
            },
            agent_id=agent_id,
        )
    stdout_thread.join(timeout=0.1)
    stderr_thread.join(timeout=0.1)


class _ObserveProxyServer:
    def __init__(self, *, run_id, upstream_base, bind_host="127.0.0.1", bind_port=0, agent_id="observer.proxy"):
        self.run_id = str(run_id or "")
        self.upstream_base = str(upstream_base or "").rstrip("/")
        self.bind_host = str(bind_host or "127.0.0.1")
        self.bind_port = int(bind_port or 0)
        self.agent_id = str(agent_id or "observer.proxy")
        self._server = None
        self._thread = None
        self.url = ""

    def start(self):
        server_ref = self

        class _Handler(BaseHTTPRequestHandler):
            def do_POST(self):
                server_ref._handle_request(self)

            def do_GET(self):
                server_ref._handle_request(self)

            def log_message(self, fmt, *args):  # noqa: A003
                return

        self._server = ThreadingHTTPServer((self.bind_host, self.bind_port), _Handler)
        actual_host, actual_port = self._server.server_address
        self.url = f"http://{actual_host}:{actual_port}/v1"
        self._thread = threading.Thread(target=self._server.serve_forever, daemon=True, name="hive-observe-proxy")
        self._thread.start()
        emit_observe_event(
            run_id=self.run_id,
            event_type="observe_proxy_started",
            outcome="success",
            payload={"proxy_url": self.url, "upstream_base": self.upstream_base, "source": "cli"},
            agent_id=self.agent_id,
        )
        return self

    def stop(self):
        if self._server:
            try:
                self._server.shutdown()
            except Exception:
                pass
            try:
                self._server.server_close()
            except Exception:
                pass
            self._server = None
        if self._thread:
            self._thread.join(timeout=1.0)
            self._thread = None
        if self.url:
            emit_observe_event(
                run_id=self.run_id,
                event_type="observe_proxy_stopped",
                outcome="success",
                payload={"proxy_url": self.url, "source": "cli"},
                agent_id=self.agent_id,
            )

    def _target_url(self, path):
        p = str(path or "/")
        if not p.startswith("/"):
            p = "/" + p
        if self.upstream_base.endswith("/v1") and p.startswith("/v1/"):
            p = p[3:]
        if self.upstream_base.endswith("/v1") and p == "/v1":
            p = ""
        return f"{self.upstream_base}{p}"

    def _handle_request(self, handler):
        started = _now_ms()
        content_length = int(handler.headers.get("Content-Length") or 0)
        body = handler.rfile.read(content_length) if content_length > 0 else b""
        path = str(handler.path or "/")

        emit_observe_event(
            run_id=self.run_id,
            event_type="observe_proxy_request",
            outcome="success",
            payload={
                "method": str(handler.command or "GET"),
                "path": path,
                "body_bytes": len(body),
                "source": "proxy",
            },
            agent_id=self.agent_id,
        )

        url = self._target_url(path)
        forward_headers = {}
        for key, value in handler.headers.items():
            k = str(key or "")
            if k.lower() in {"host", "content-length"}:
                continue
            forward_headers[k] = value
        req = urllib.request.Request(url=url, data=(body if body else None), headers=forward_headers, method=handler.command)
        try:
            with urllib.request.urlopen(req, timeout=120) as resp:
                resp_body = resp.read()
                status_code = int(resp.status)
                resp_headers = dict(resp.headers.items())
            elapsed = max(0, _now_ms() - started)
            emit_observe_event(
                run_id=self.run_id,
                event_type="observe_proxy_response",
                outcome="success",
                payload={
                    "method": str(handler.command or "GET"),
                    "path": path,
                    "status_code": status_code,
                    "duration_ms": elapsed,
                    "body_tail": _shorten(resp_body.decode("utf-8", errors="replace"), limit=2000),
                    "source": "proxy",
                },
                agent_id=self.agent_id,
            )
            handler.send_response(status_code)
            for header_key, header_value in resp_headers.items():
                hk = str(header_key or "")
                if hk.lower() in {"transfer-encoding", "content-length", "connection"}:
                    continue
                handler.send_header(hk, header_value)
            handler.send_header("Content-Length", str(len(resp_body)))
            handler.end_headers()
            if resp_body:
                handler.wfile.write(resp_body)
        except urllib.error.HTTPError as exc:
            err_body = exc.read() if hasattr(exc, "read") else b""
            status_code = int(getattr(exc, "code", 502) or 502)
            elapsed = max(0, _now_ms() - started)
            emit_observe_event(
                run_id=self.run_id,
                event_type="observe_proxy_response",
                outcome="failed",
                payload={
                    "method": str(handler.command or "GET"),
                    "path": path,
                    "status_code": status_code,
                    "duration_ms": elapsed,
                    "error": _shorten(str(exc), limit=400),
                    "body_tail": _shorten(err_body.decode("utf-8", errors="replace"), limit=2000),
                    "source": "proxy",
                },
                agent_id=self.agent_id,
            )
            handler.send_response(status_code)
            handler.send_header("Content-Type", "application/json")
            handler.send_header("Content-Length", str(len(err_body)))
            handler.end_headers()
            if err_body:
                handler.wfile.write(err_body)
        except Exception as exc:
            elapsed = max(0, _now_ms() - started)
            payload = json.dumps({"error": "proxy_forward_failed", "detail": str(exc)}, separators=(",", ":")).encode("utf-8")
            emit_observe_event(
                run_id=self.run_id,
                event_type="observe_proxy_response",
                outcome="failed",
                payload={
                    "method": str(handler.command or "GET"),
                    "path": path,
                    "status_code": 502,
                    "duration_ms": elapsed,
                    "error": _shorten(str(exc), limit=400),
                    "source": "proxy",
                },
                agent_id=self.agent_id,
            )
            handler.send_response(502)
            handler.send_header("Content-Type", "application/json")
            handler.send_header("Content-Length", str(len(payload)))
            handler.end_headers()
            handler.wfile.write(payload)


@contextmanager
def observe_proxy(*, run_id, upstream_base, bind_host="127.0.0.1", bind_port=0, agent_id="observer.proxy"):
    server = _ObserveProxyServer(
        run_id=run_id,
        upstream_base=upstream_base,
        bind_host=bind_host,
        bind_port=bind_port,
        agent_id=agent_id,
    ).start()
    try:
        yield server
    finally:
        server.stop()


def _build_proxy_env(proxy_url):
    url = str(proxy_url or "").strip().rstrip("/")
    return {
        "OPENAI_BASE_URL": url,
        "OPENAI_API_BASE": url,
    }


def run_command_observed(
    command,
    *,
    run_id=None,
    run_name=None,
    cwd=None,
    timeout_sec=None,
    agent_id="observer.cli",
    open_ui_url="",
    env_overrides=None,
    record_fields=None,
):
    if isinstance(command, str):
        command_text = command.strip()
        if not command_text:
            raise ValueError("command must be non-empty")
        argv = shlex.split(command_text, posix=(os.name != "nt"))
    else:
        argv = [str(part) for part in (command or []) if str(part)]
        command_text = " ".join(argv).strip()
    if not argv:
        raise ValueError("command must be non-empty")

    rid = str(run_id or _build_run_id("observe-run"))
    started_at = _now_ms()
    record = {
        "run_id": rid,
        "run_name": str(run_name or command_text),
        "command": command_text,
        "argv": list(argv),
        "cwd": str(cwd or ""),
        "trace_log_path": _active_trace_log_path(),
        "archived": False,
        "archived_at_ms": None,
        "archive_reason": None,
        "archive_actor": None,
        "started_at_ms": started_at,
        "last_update_ms": started_at,
        "status": "running",
        "exit_code": None,
        "duration_ms": None,
        "source": "cli",
    }
    record.update(dict(record_fields or {}))
    _persist_run_record(record)
    emit_observe_event(
        run_id=rid,
        event_type="observe_run_started",
        outcome="success",
        payload={
            "run_name": record["run_name"],
            "command": command_text,
            "cwd": record["cwd"],
            "source": "cli",
            **{
                key: value
                for key, value in (dict(record_fields or {})).items()
                if key in {"replay_of_run_id"}
            },
        },
        agent_id=agent_id,
    )
    emit_observe_event(
        run_id=rid,
        event_type="observe_step",
        outcome="success",
        payload={"step_name": "command.start", "command": command_text, "source": "cli"},
        agent_id=agent_id,
    )

    proc = subprocess.Popen(
        argv,
        cwd=cwd or None,
        env={**os.environ, **dict(env_overrides or {})},
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
        errors="replace",
        bufsize=1,
        universal_newlines=True,
        shell=False,
    )
    timed_out = False
    try:
        _stream_process_output(proc, rid, command_text, agent_id, heartbeat_cb=lambda: _touch_run_record(rid))
        proc.wait(timeout=timeout_sec if timeout_sec and timeout_sec > 0 else None)
    except subprocess.TimeoutExpired:
        timed_out = True
        proc.kill()
        proc.wait(timeout=2)
    finally:
        try:
            if proc.stdout:
                proc.stdout.close()
        except Exception:
            pass
        try:
            if proc.stderr:
                proc.stderr.close()
        except Exception:
            pass

    elapsed_ms = max(0, _now_ms() - started_at)
    exit_code = int(proc.returncode if proc.returncode is not None else 1)
    if timed_out:
        exit_code = 124
    ok = (not timed_out) and exit_code == 0
    outcome = "success" if ok else "failed"

    emit_observe_event(
        run_id=rid,
        event_type="observe_step",
        outcome=outcome,
        payload={
            "step_name": "command.finish",
            "duration_ms": elapsed_ms,
            "exit_code": exit_code,
            "timeout_sec": timeout_sec if timed_out else None,
            "source": "cli",
        },
        agent_id=agent_id,
    )
    emit_observe_event(
        run_id=rid,
        event_type="observe_run_finished",
        outcome=outcome,
        payload={
            "run_name": record["run_name"],
            "command": command_text,
            "duration_ms": elapsed_ms,
            "exit_code": exit_code,
            "error": "timeout_expired" if timed_out else "",
            "source": "cli",
            **{
                key: value
                for key, value in (dict(record_fields or {})).items()
                if key in {"replay_of_run_id"}
            },
        },
        agent_id=agent_id,
    )
    record["status"] = "success" if ok else "failed"
    record["exit_code"] = exit_code
    record["duration_ms"] = elapsed_ms
    record["finished_at_ms"] = _now_ms()
    _persist_run_record(record)

    if open_ui_url:
        _open_run_in_ui(open_ui_url, rid)
    return {
        "run_id": rid,
        "exit_code": exit_code,
        "duration_ms": elapsed_ms,
        "status": record["status"],
    }


def _build_parser():
    parser = argparse.ArgumentParser(
        prog="hive",
        description="HiveOS Trace harness: wrap workflows, inspect routes, and open runs.",
    )
    parser.add_argument(
        "--version",
        action="version",
        version=f"hive {_hive_version()}",
        help="Show installed hive CLI version.",
    )
    top = parser.add_subparsers(dest="command")

    quickstart_parser = top.add_parser("quickstart", help="Run a 60-second local smoke flow (run -> ls hint -> open hint).")
    quickstart_parser.add_argument("--open", action="store_true", help="Open local UI after run completion.")
    quickstart_parser.add_argument("--no-open", action="store_true", help="Do not open local UI automatically.")
    quickstart_parser.add_argument("--ui-url", default="", help="Override local UI URL.")

    doctor_parser = top.add_parser("doctor", help="Run local preflight checks for hive trace.")
    doctor_parser.add_argument("--ui-url", default="", help="Optional UI URL to check (default: http://localhost:5173).")

    trace_parser = top.add_parser("trace", help="Trace harness commands.")
    trace_sub = trace_parser.add_subparsers(dest="trace_command")

    run_parser = trace_sub.add_parser("run", help="Run a command with trace capture.")
    run_parser.add_argument("--name", dest="run_name", default="", help="Optional run label.")
    run_parser.add_argument("--cwd", default="", help="Working directory for the command.")
    run_parser.add_argument("--timeout-sec", type=int, default=0, help="Command timeout in seconds.")
    run_parser.add_argument("--open", action="store_true", help="Open local UI after run completion.")
    run_parser.add_argument("--no-open", action="store_true", help="Do not open local UI automatically.")
    run_parser.add_argument("--ui-url", default="", help="Override local UI URL.")
    run_parser.add_argument("--proxy", action="store_true", help="Enable OpenAI-compatible local proxy capture.")
    run_parser.add_argument(
        "--proxy-upstream",
        default="",
        help="Upstream OpenAI-compatible base URL (default: OPENAI_BASE_URL env or https://api.openai.com/v1).",
    )
    run_parser.add_argument("--proxy-port", type=int, default=0, help="Local proxy port (default: auto).")
    run_parser.add_argument("argv", nargs=argparse.REMAINDER, help="Command after '--', e.g. -- python agent.py")

    replay_parser = trace_sub.add_parser("replay", help="Replay a prior run using its captured command.")
    replay_parser.add_argument("run_id", help="Run ID to replay.")
    replay_parser.add_argument("--name", dest="run_name", default="", help="Optional replay run label.")
    replay_parser.add_argument("--cwd", default="", help="Override working directory.")
    replay_parser.add_argument("--timeout-sec", type=int, default=0, help="Replay timeout in seconds.")
    replay_parser.add_argument("--open", action="store_true", help="Open local UI after replay completion.")
    replay_parser.add_argument("--no-open", action="store_true", help="Do not open local UI automatically.")
    replay_parser.add_argument("--ui-url", default="", help="Override local UI URL.")

    ls_parser = trace_sub.add_parser("ls", help="List recent traced runs.")
    ls_parser.add_argument("--limit", type=int, default=30, help="Maximum runs to list.")
    ls_parser.add_argument(
        "--status",
        default="all",
        choices=["all", "active", "archived", "running", "success", "failed"],
        help="Filter listed runs by lifecycle status.",
    )
    ls_parser.add_argument("--json", action="store_true", help="Print run records as JSON lines.")

    show_parser = trace_sub.add_parser("show", help="Show detailed events for one run.")
    show_parser.add_argument("run_id", help="Run ID to inspect.")
    show_parser.add_argument("--limit", type=int, default=200, help="Maximum events to show.")
    show_parser.add_argument("--json", action="store_true", help="Print event documents as JSON lines.")

    tail_parser = trace_sub.add_parser("tail", help="Tail recent trace events, optionally following updates.")
    tail_parser.add_argument("--run-id", default="", help="Optional run ID filter.")
    tail_parser.add_argument("--limit", type=int, default=50, help="Maximum events to emit per poll.")
    tail_parser.add_argument("--follow", action="store_true", help="Keep polling and print newly observed events.")
    tail_parser.add_argument("--json", action="store_true", help="Print event documents as JSON lines.")

    export_parser = trace_sub.add_parser("export", help="Export a run record and events to a shareable bundle.")
    export_parser.add_argument("run_id", help="Run ID to export.")
    export_parser.add_argument("--bundle", required=True, help="Bundle file path to write.")

    import_parser = trace_sub.add_parser("import", help="Import a run bundle into local trace storage.")
    import_parser.add_argument("--bundle", required=True, help="Bundle file path to read.")
    import_parser.add_argument("--as-run-id", default="", help="Optional replacement run ID on import.")

    summary_parser = trace_sub.add_parser("summary", help="Show compact run summary with output tails.")
    summary_parser.add_argument("run_id", help="Run ID to summarize.")
    summary_parser.add_argument("--event-limit", type=int, default=2000, help="Maximum events to inspect.")
    summary_parser.add_argument("--tail-lines", type=int, default=20, help="Tail lines to include per stream.")
    summary_parser.add_argument("--json", action="store_true", help="Print run summary as JSON.")

    diff_parser = trace_sub.add_parser("diff", help="Compare two runs and highlight key deltas.")
    diff_parser.add_argument("run_id_a", help="First run ID.")
    diff_parser.add_argument("run_id_b", help="Second run ID.")
    diff_parser.add_argument("--event-limit", type=int, default=2000, help="Maximum events to inspect per run.")
    diff_parser.add_argument("--tail-lines", type=int, default=20, help="Output tail lines to compare per stream.")
    diff_parser.add_argument("--json", action="store_true", help="Print diff summary as JSON.")

    diagnose_parser = trace_sub.add_parser("diagnose", help="Diagnose one run and suggest next actions.")
    diagnose_parser.add_argument("run_id", help="Run ID to diagnose.")
    diagnose_parser.add_argument("--event-limit", type=int, default=2000, help="Maximum events to inspect.")
    diagnose_parser.add_argument("--tail-lines", type=int, default=20, help="Tail lines to include in summary.")
    diagnose_parser.add_argument("--json", action="store_true", help="Print diagnosis as JSON.")

    open_parser = trace_sub.add_parser("open", help="Open a run in local UI.")
    open_parser.add_argument("run_id", help="Run ID to open.")
    open_parser.add_argument("--ui-url", default="", help="Override local UI URL.")

    archive_parser = trace_sub.add_parser("archive", help="Archive a traced run.")
    archive_parser.add_argument("run_id", help="Run ID to archive.")
    archive_parser.add_argument("--reason", default="", help="Optional archive reason.")
    archive_parser.add_argument("--actor", default="operator", help="Actor label for auditability.")

    unarchive_parser = trace_sub.add_parser("unarchive", help="Restore an archived run to active view.")
    unarchive_parser.add_argument("run_id", help="Run ID to unarchive.")

    prune_parser = trace_sub.add_parser("prune", help="Delete old run records and optionally drop event payloads.")
    prune_parser.add_argument(
        "--older-than",
        required=True,
        help="Age threshold (e.g. 30d, 12h, 45m, 3600s). Bare number means days.",
    )
    prune_parser.add_argument(
        "--include-active",
        action="store_true",
        help="Also prune non-archived runs older than threshold (default prunes archived only).",
    )
    prune_parser.add_argument(
        "--drop-events",
        action="store_true",
        help="Also drop matching run events from associated trace log files.",
    )

    reconcile_parser = trace_sub.add_parser("reconcile", help="Reconcile stale running runs to a terminal status.")
    reconcile_parser.add_argument(
        "--stale-after",
        default="",
        help="Stale window (e.g. 5m, 120s). Bare number means seconds.",
    )
    reconcile_parser.add_argument("--json", action="store_true", help="Print reconciliation summary as JSON.")

    return parser


def _print_trace_rows(events):
    if not events:
        print("No trace events matched.")
        return
    for event in events:
        ts = str(event.get("timestamp") or "-")
        outcome = str(event.get("outcome") or "-")
        event_type = str(event.get("event_type") or "-")
        run_id = str(event.get("run_id") or "-")
        payload = event.get("payload") if isinstance(event.get("payload"), dict) else {}
        step_name = str(payload.get("step_name") or payload.get("run_name") or "").strip()
        suffix = f" | {step_name}" if step_name else ""
        print(f"{ts} | {outcome:<7} | {event_type:<22} | {run_id}{suffix}")


def _handle_legacy_trace_events(args):
    limit = max(1, min(1000, int(args.limit or 50)))
    filters = {
        "run_id": str(args.run_id or "").strip() or None,
        "event_type": str(args.event_type or "").strip() or None,
        "outcome": str(args.outcome or "").strip() or None,
        "agent_id": str(args.agent_id or "").strip() or None,
        "task_id": str(args.task_id or "").strip() or None,
        "chunk_id": str(args.chunk_id or "").strip() or None,
        "zone_id": str(args.zone_id or "").strip() or None,
    }
    events = read_trace_events(limit=limit, filters=filters)
    if args.json:
        for event in events:
            print(json.dumps(event, separators=(",", ":")))
    else:
        _print_trace_rows(events)
    return 0


def _build_ui_url(base_url, run_id):
    base = str(base_url or "").strip() or "http://localhost:5173"
    joiner = "&" if "?" in base else "?"
    return f"{base}{joiner}run_id={quote_plus(str(run_id))}"


def _resolve_ui_url_for_command(args):
    explicit_open = bool(getattr(args, "open", False))
    explicit_no_open = bool(getattr(args, "no_open", False))
    if explicit_open and explicit_no_open:
        raise SystemExit("Choose only one of --open or --no-open")
    default_open = bool(getattr(hive_config, "TRACE_OPEN_ON_RUN", False))
    should_open = explicit_open or (default_open and not explicit_no_open)
    if not should_open:
        return ""
    return str(getattr(args, "ui_url", "") or os.environ.get("HIVE_TRACE_UI_URL") or "http://localhost:5173").strip()


def _open_run_in_ui(base_url, run_id):
    url = _build_ui_url(base_url, run_id)
    if str(os.environ.get("HIVE_TRACE_DISABLE_BROWSER") or "").strip() in {"1", "true", "yes"}:
        return url
    ui_ok, _ = _check_ui_reachable(base_url)
    if not ui_ok:
        return _open_run_status_fallback(url, run_id)
    try:
        webbrowser.open(url, new=2)
    except Exception:
        pass
    return url


def _build_status_page_html(*, run_id, run_record, target_url):
    record = dict(run_record or {})
    rid_text = str(run_id or "")
    status = html.escape(str(record.get("status") or "unknown"))
    command = html.escape(str(record.get("command") or ""))
    cwd = html.escape(str(record.get("cwd") or ""))
    started = html.escape(str(record.get("started_at_ms") or ""))
    finished = html.escape(str(record.get("finished_at_ms") or ""))
    trace_log_path_raw = str(record.get("trace_log_path") or _active_trace_log_path())
    trace_path = html.escape(trace_log_path_raw)
    run_file = html.escape(str(_run_file(run_id)))
    target = html.escape(str(target_url or ""))
    rid = html.escape(rid_text)
    duration_ms = int(record.get("duration_ms") or 0)
    duration = html.escape(f"{duration_ms} ms")
    exit_code = html.escape(str(record.get("exit_code") if record.get("exit_code") is not None else ""))

    events = _read_trace_events_for_run(rid_text, limit=120, trace_log_path=trace_log_path_raw)
    recent_events = events[-15:]
    stdout_tail = []
    stderr_tail = []
    for event in events:
        event_type = str(event.get("event_type") or "")
        payload = event.get("payload")
        payload = payload if isinstance(payload, dict) else {}
        text = str(payload.get("text") or "").strip()
        if not text:
            continue
        if event_type == "observe_stdout":
            stdout_tail.append(text)
        elif event_type == "observe_stderr":
            stderr_tail.append(text)
    stdout_tail = stdout_tail[-20:]
    stderr_tail = stderr_tail[-20:]

    recent_rows = []
    for event in recent_events:
        event_type = str(event.get("event_type") or "")
        outcome = str(event.get("outcome") or "")
        payload = event.get("payload")
        payload = payload if isinstance(payload, dict) else {}
        payload_text = _shorten(json.dumps(payload, separators=(",", ":")), limit=220)
        line = f"{event_type} | {outcome} | {payload_text}"
        recent_rows.append(html.escape(line))
    recent_rows_html = "<br/>".join(recent_rows) if recent_rows else "<em>(no events)</em>"
    stdout_html = "<br/>".join(html.escape(item) for item in stdout_tail) if stdout_tail else "<em>(none)</em>"
    stderr_html = "<br/>".join(html.escape(item) for item in stderr_tail) if stderr_tail else "<em>(none)</em>"

    peer_records = [item for item in _list_run_records(limit=12) if str(item.get("run_id") or "").strip() != rid_text][:8]
    peer_lines = []
    for item in peer_records:
        peer_id = str(item.get("run_id") or "").strip()
        if not peer_id:
            continue
        peer_status = str(item.get("status") or "unknown")
        peer_duration = int(item.get("duration_ms") or 0)
        peer_cmd = _shorten(str(item.get("command") or ""), limit=90)
        peer_lines.append(html.escape(f"{peer_id} | {peer_status} | {peer_duration} ms | {peer_cmd}"))
    peers_html = "<br/>".join(peer_lines) if peer_lines else "<em>(no other runs)</em>"

    action_lines = [
        f"hive trace show {rid_text} --limit 120",
        f"hive trace diagnose {rid_text}",
        f"hive trace replay {rid_text} --no-open",
        "hive trace ls --limit 10",
    ]
    actions_html = "<br/>".join(html.escape(line) for line in action_lines)
    return f"""<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Hive Trace Status</title>
  <style>
    body {{ background: #0d0d0f; color: #e8e8ea; font-family: Segoe UI, Arial, sans-serif; margin: 0; padding: 20px; }}
    .card {{ border: 1px solid #3a3a3f; border-radius: 12px; padding: 16px; background: #17171a; max-width: 1040px; }}
    h1 {{ margin: 0 0 8px; font-size: 20px; color: #f4f4f5; }}
    h2 {{ margin: 16px 0 8px; font-size: 15px; color: #e3e3e6; }}
    p {{ margin: 8px 0; color: #b4b4ba; }}
    .kv {{ margin-top: 12px; font-family: Consolas, Menlo, monospace; white-space: pre; overflow-x: auto; }}
    .row {{ margin: 4px 0; }}
    .block {{ margin-top: 12px; border: 1px solid #2f2f34; border-radius: 10px; padding: 10px; background: #101013; overflow-x: auto; }}
    a {{ color: #d0d0d6; }}
    code {{ color: #f1f1f2; }}
  </style>
</head>
<body>
  <div class="card">
    <h1>Hive Trace Status</h1>
    <p>Frontend UI is unreachable right now, so this local fallback page was served by Hive.</p>
    <div class="kv">
      <div class="row"><strong>run_id:</strong> <code>{rid}</code></div>
      <div class="row"><strong>status:</strong> <code>{status}</code></div>
      <div class="row"><strong>command:</strong> <code>{command}</code></div>
      <div class="row"><strong>cwd:</strong> <code>{cwd}</code></div>
      <div class="row"><strong>started_at_ms:</strong> <code>{started}</code></div>
      <div class="row"><strong>finished_at_ms:</strong> <code>{finished}</code></div>
      <div class="row"><strong>duration:</strong> <code>{duration}</code></div>
      <div class="row"><strong>exit_code:</strong> <code>{exit_code}</code></div>
      <div class="row"><strong>run_file:</strong> <code>{run_file}</code></div>
      <div class="row"><strong>trace_log_path:</strong> <code>{trace_path}</code></div>
      <div class="row"><strong>target_ui:</strong> <a href="{target}" target="_blank" rel="noopener noreferrer">{target}</a></div>
    </div>
    <h2>CLI Actions</h2>
    <div class="block kv">{actions_html}</div>
    <h2>Recent Event Stream</h2>
    <div class="block kv">{recent_rows_html}</div>
    <h2>stdout tail</h2>
    <div class="block kv">{stdout_html}</div>
    <h2>stderr tail</h2>
    <div class="block kv">{stderr_html}</div>
    <h2>Recent Runs</h2>
    <div class="block kv">{peers_html}</div>
    <p>Once your frontend is running, open the target UI link above.</p>
  </div>
</body>
</html>"""


def _open_run_status_fallback(target_url, run_id):
    served = threading.Event()
    record = _read_run_record(run_id) or {}
    page_html = _build_status_page_html(run_id=run_id, run_record=record, target_url=target_url)

    class _StatusHandler(BaseHTTPRequestHandler):
        def do_GET(self):  # noqa: N802
            payload = page_html.encode("utf-8")
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Content-Length", str(len(payload)))
            self.end_headers()
            self.wfile.write(payload)
            served.set()

        def log_message(self, fmt, *args):  # noqa: A003
            return

    server = ThreadingHTTPServer(("127.0.0.1", 0), _StatusHandler)
    host, port = server.server_address
    fallback_url = f"http://{host}:{port}/?run_id={quote_plus(str(run_id))}"
    timeout_sec = max(0.5, float(os.environ.get("HIVE_TRACE_STATUS_PAGE_WAIT_SEC") or 1.5))

    def _serve():
        deadline = time.time() + timeout_sec
        server.timeout = 0.25
        try:
            while time.time() < deadline and not served.is_set():
                server.handle_request()
        finally:
            try:
                server.server_close()
            except Exception:
                pass

    thread = threading.Thread(target=_serve, daemon=True, name="hive-observe-status-page")
    thread.start()
    try:
        webbrowser.open(fallback_url, new=2)
    except Exception:
        pass
    thread.join(timeout=timeout_sec + 0.25)
    return fallback_url


def _handle_trace_run(args):
    raw = list(args.argv or [])
    if raw and raw[0] == "--":
        raw = raw[1:]
    if not raw:
        raise SystemExit("No command provided. Example: hive trace run -- python agent.py")
    run_id = _build_run_id("observe-run")
    ui_url = _resolve_ui_url_for_command(args)
    proxy_enabled = bool(args.proxy)
    proxy_upstream = str(
        args.proxy_upstream
        or os.environ.get("HIVE_TRACE_PROXY_UPSTREAM")
        or os.environ.get("OPENAI_BASE_URL")
        or "https://api.openai.com/v1"
    ).strip()
    env_overrides = {}
    proxy_context = None
    if proxy_enabled:
        proxy_context = observe_proxy(
            run_id=run_id,
            upstream_base=proxy_upstream,
            bind_host="127.0.0.1",
            bind_port=int(args.proxy_port or 0),
            agent_id="observer.proxy",
        )
    if proxy_context is None:
        result = run_command_observed(
            raw,
            run_id=run_id,
            run_name=args.run_name or None,
            cwd=args.cwd or None,
            timeout_sec=args.timeout_sec or None,
            agent_id="observer.cli",
            open_ui_url=ui_url,
            env_overrides=env_overrides,
        )
    else:
        with proxy_context as proxy_server:
            env_overrides = _build_proxy_env(proxy_server.url)
            result = run_command_observed(
                raw,
                run_id=run_id,
                run_name=args.run_name or None,
                cwd=args.cwd or None,
                timeout_sec=args.timeout_sec or None,
                agent_id="observer.cli",
                open_ui_url=ui_url,
                env_overrides=env_overrides,
            )
            result["proxy_url"] = proxy_server.url
    print(f"run_id={result['run_id']} status={result['status']} duration_ms={result['duration_ms']}")
    if result.get("proxy_url"):
        print(f"proxy={result['proxy_url']}")
    if ui_url:
        print(f"ui={_build_ui_url(ui_url, result['run_id'])}")
    return int(result["exit_code"])


def _handle_trace_replay(args):
    base_run_id = str(args.run_id or "").strip()
    if not base_run_id:
        raise SystemExit("run_id is required")
    base = _read_run_record(base_run_id)
    if not base:
        raise SystemExit(f"run_id not found: {base_run_id}")
    base_argv = base.get("argv")
    if isinstance(base_argv, list) and base_argv:
        replay_command = [str(part) for part in base_argv if str(part)]
    else:
        replay_command = str(base.get("command") or "").strip()
    if not replay_command:
        raise SystemExit(f"run command missing for run_id: {base_run_id}")
    ui_url = _resolve_ui_url_for_command(args)
    run_name = str(args.run_name or "").strip()
    if not run_name:
        run_name = f"replay:{base_run_id}"
    replay_cwd = str(args.cwd or "").strip() or str(base.get("cwd") or "").strip() or None
    result = run_command_observed(
        replay_command,
        run_name=run_name,
        cwd=replay_cwd,
        timeout_sec=args.timeout_sec or None,
        agent_id="observer.replay",
        open_ui_url=ui_url,
        record_fields={"replay_of_run_id": base_run_id, "source": "replay"},
    )
    print(
        f"run_id={result['run_id']} status={result['status']} duration_ms={result['duration_ms']} replay_of={base_run_id}"
    )
    if ui_url:
        print(f"ui={_build_ui_url(ui_url, result['run_id'])}")
    return int(result["exit_code"])


def _format_age_ms(age_ms):
    sec = max(0, int(age_ms / 1000))
    if sec < 60:
        return f"{sec}s"
    mins = sec // 60
    if mins < 60:
        return f"{mins}m"
    hrs = mins // 60
    if hrs < 24:
        return f"{hrs}h"
    return f"{hrs // 24}d"


def _handle_trace_ls(args):
    _maybe_auto_reconcile()
    records = _list_run_records(limit=args.limit)
    records = _filter_records_by_status(records, args.status)
    if args.json:
        for record in records:
            print(json.dumps(record, separators=(",", ":")))
        return 0
    if not records:
        print("No traced runs found.")
        return 0
    now = _now_ms()
    for rec in records:
        run_id = str(rec.get("run_id") or "-")
        status = _record_effective_status(rec)
        started = int(rec.get("started_at_ms") or 0)
        age = _format_age_ms(now - started) if started > 0 else "--"
        command = _shorten(rec.get("command"), limit=100)
        print(f"{run_id} | {status:<7} | {age:>4} | {command}")
    return 0


def _handle_legacy_trace_events_argv(argv):
    parser = argparse.ArgumentParser(prog="hive trace-events")
    parser.add_argument("--run-id", default="")
    parser.add_argument("--event-type", default="")
    parser.add_argument("--outcome", default="")
    parser.add_argument("--agent-id", default="")
    parser.add_argument("--task-id", default="")
    parser.add_argument("--chunk-id", default="")
    parser.add_argument("--zone-id", default="")
    parser.add_argument("--limit", type=int, default=50)
    parser.add_argument("--json", action="store_true")
    args = parser.parse_args(argv)
    return _handle_legacy_trace_events(args)


def _handle_trace_show(args):
    _maybe_auto_reconcile()
    run_id = str(args.run_id or "").strip()
    if not run_id:
        raise SystemExit("run_id is required")
    limit = max(1, min(1000, int(args.limit or 200)))
    record = _read_run_record(run_id) or {}
    preferred_log = str(record.get("trace_log_path") or "").strip()
    events = _read_trace_events_for_run(run_id, limit=limit, trace_log_path=preferred_log)
    if not events:
        # Backward-compatible fallback for older runs without trace_log_path.
        events = read_trace_events(limit=limit, filters={"run_id": run_id})
    if args.json:
        for event in events:
            print(json.dumps(event, separators=(",", ":")))
        return 0
    _print_trace_rows(events)
    return 0


def _event_identity(event):
    trace_id = str(event.get("trace_id") or "").strip()
    if trace_id:
        return trace_id
    return json.dumps(
        {
            "timestamp": event.get("timestamp"),
            "run_id": event.get("run_id"),
            "event_type": event.get("event_type"),
            "outcome": event.get("outcome"),
            "payload": event.get("payload"),
        },
        sort_keys=True,
        separators=(",", ":"),
    )


def _read_tail_events(limit=50, run_id=""):
    bounded = max(1, min(20000, int(limit or 50)))
    filters = {}
    run_key = str(run_id or "").strip()
    if run_key:
        filters["run_id"] = run_key
    return read_trace_events(limit=bounded, filters=filters)


def _emit_events(events, *, as_json=False):
    if as_json:
        for event in events:
            print(json.dumps(event, separators=(",", ":")))
        return
    _print_trace_rows(events)


def _handle_trace_tail(args):
    _maybe_auto_reconcile()
    limit = max(1, min(20000, int(args.limit or 50)))
    run_id = str(args.run_id or "").strip()
    events = _read_tail_events(limit=limit, run_id=run_id)
    _emit_events(events, as_json=bool(args.json))
    if not bool(args.follow):
        return 0

    seen = set()
    for event in events:
        seen.add(_event_identity(event))
    try:
        while True:
            time.sleep(1.0)
            current = _read_tail_events(limit=limit, run_id=run_id)
            fresh = []
            for event in current:
                key = _event_identity(event)
                if key in seen:
                    continue
                fresh.append(event)
                seen.add(key)
            if fresh:
                _emit_events(fresh, as_json=bool(args.json))
    except KeyboardInterrupt:
        return 0


def _handle_trace_export(args):
    _maybe_auto_reconcile()
    run_id = str(args.run_id or "").strip()
    if not run_id:
        raise SystemExit("run_id is required")
    record = _read_run_record(run_id)
    if not record:
        raise SystemExit(f"run_id not found: {run_id}")
    preferred_log = str(record.get("trace_log_path") or "").strip()
    events = _read_all_trace_events_for_run(run_id, trace_log_path=preferred_log)
    bundle_path = Path(str(args.bundle or "").strip())
    if not str(bundle_path):
        raise SystemExit("--bundle is required")
    bundle_path.parent.mkdir(parents=True, exist_ok=True)
    payload = {
        "hive_trace_bundle_version": TRACE_BUNDLE_VERSION,
        "exported_at": _now_ms(),
        "source": {
            "trace_log_path": preferred_log or _active_trace_log_path(),
            "trace_home": str(_trace_home()),
        },
        "run_record": record,
        "events": events,
    }
    bundle_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
    print(f"exported={run_id} events={len(events)} bundle={bundle_path}")
    return 0


def _load_and_validate_bundle(path):
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except Exception as exc:
        raise SystemExit(f"invalid bundle json: {_shorten(exc, limit=200)}")
    if not isinstance(payload, dict):
        raise SystemExit("invalid bundle: expected object")
    version = int(payload.get("hive_trace_bundle_version") or 0)
    if version != TRACE_BUNDLE_VERSION:
        raise SystemExit(
            f"unsupported bundle version: {version} (expected {TRACE_BUNDLE_VERSION})"
        )
    record = payload.get("run_record")
    events = payload.get("events")
    if not isinstance(record, dict):
        raise SystemExit("invalid bundle: run_record must be an object")
    if not isinstance(events, list):
        raise SystemExit("invalid bundle: events must be an array")
    run_id = str(record.get("run_id") or "").strip()
    if not run_id:
        raise SystemExit("invalid bundle: run_record.run_id is required")
    out_events = []
    for item in events:
        if not isinstance(item, dict):
            continue
        if str(item.get("run_id") or "").strip() != run_id:
            continue
        out_events.append(item)
    return {"record": record, "events": out_events, "run_id": run_id}


def _append_events_to_active_trace_log(events):
    if not events:
        return 0
    path = Path(_active_trace_log_path())
    path.parent.mkdir(parents=True, exist_ok=True)
    written = 0
    with path.open("a", encoding="utf-8") as handle:
        for event in events:
            handle.write(json.dumps(event, separators=(",", ":")) + "\n")
            written += 1
    return written


def _handle_trace_import(args):
    _maybe_auto_reconcile()
    bundle_path = Path(str(args.bundle or "").strip())
    if not str(bundle_path):
        raise SystemExit("--bundle is required")
    if not bundle_path.exists():
        raise SystemExit(f"bundle not found: {bundle_path}")
    loaded = _load_and_validate_bundle(bundle_path)
    source_run_id = loaded["run_id"]
    target_run_id = str(args.as_run_id or "").strip() or source_run_id
    if not target_run_id:
        raise SystemExit("target run_id is required")
    if _read_run_record(target_run_id):
        raise SystemExit(f"run_id already exists: {target_run_id}")

    record = dict(loaded["record"])
    events = [dict(item) for item in loaded["events"]]
    if target_run_id != source_run_id:
        record["run_id"] = target_run_id
        if str(record.get("replay_of_run_id") or "").strip() == source_run_id:
            record["replay_of_run_id"] = target_run_id
        for event in events:
            event["run_id"] = target_run_id

    now = _now_ms()
    record["imported_from_bundle"] = True
    record["imported_at_ms"] = now
    record["import_source_run_id"] = source_run_id
    record["trace_log_path"] = _active_trace_log_path()
    record["last_update_ms"] = now

    _persist_run_record(record)
    imported_events = _append_events_to_active_trace_log(events)
    print(f"imported={target_run_id} events={imported_events} bundle={bundle_path}")
    return 0


def _handle_trace_summary(args):
    _maybe_auto_reconcile()
    run_id = str(args.run_id or "").strip()
    if not run_id:
        raise SystemExit("run_id is required")
    record = _read_run_record(run_id)
    if not record:
        raise SystemExit(f"run_id not found: {run_id}")
    limit = max(1, min(20000, int(args.event_limit or 2000)))
    tail_lines = max(1, min(200, int(args.tail_lines or 20)))
    preferred_log = str(record.get("trace_log_path") or "").strip()
    events = _read_trace_events_for_run(run_id, limit=limit, trace_log_path=preferred_log)
    if not events:
        events = read_trace_events(limit=limit, filters={"run_id": run_id})
    summary = _build_run_summary(record, events, tail_lines=tail_lines)
    if args.json:
        print(json.dumps(summary, separators=(",", ":")))
        return 0
    print(
        f"run_id={summary['run_id']} status={summary['status']} "
        f"exit_code={summary['exit_code']} duration_ms={summary['duration_ms']} events={summary['event_count']}"
    )
    print(f"command={summary['command']}")
    print(f"cwd={summary['cwd']}")
    if summary["stderr_tail"]:
        print("stderr_tail:")
        for line in summary["stderr_tail"]:
            print(f"  {line}")
    if summary["stdout_tail"]:
        print("stdout_tail:")
        for line in summary["stdout_tail"]:
            print(f"  {line}")
    print("suggestions:")
    for item in summary["suggestions"]:
        print(f"  - {item}")
    return 0


def _collect_output_lines(events):
    stdout_lines = []
    stderr_lines = []
    for event in events:
        if str(event.get("event_type") or "") != "observe_output":
            continue
        payload = event.get("payload") if isinstance(event.get("payload"), dict) else {}
        text = str(payload.get("text") or "")
        if not text:
            continue
        stream = str(payload.get("stream") or "stdout").strip().lower()
        if stream == "stderr":
            stderr_lines.append(text)
        else:
            stdout_lines.append(text)
    return {"stdout": stdout_lines, "stderr": stderr_lines}


def _build_run_summary(record, events, tail_lines=20):
    t = max(1, int(tail_lines or 20))
    run_id = str(record.get("run_id") or "")
    status = _record_effective_status(record)
    raw_exit_code = record.get("exit_code")
    exit_code = int(raw_exit_code) if raw_exit_code is not None else None
    outputs = _collect_output_lines(events)
    suggestions = [
        f"hive trace show {run_id} --limit 120",
        f"hive trace diagnose {run_id}",
        f"hive trace replay {run_id} --no-open",
        "hive trace ls --limit 10",
    ]
    return {
        "run_id": run_id,
        "status": status,
        "exit_code": exit_code,
        "duration_ms": int(record.get("duration_ms") or 0),
        "event_count": len(events),
        "command": str(record.get("command") or ""),
        "cwd": str(record.get("cwd") or ""),
        "stderr_tail": outputs["stderr"][-t:],
        "stdout_tail": outputs["stdout"][-t:],
        "suggestions": suggestions,
    }


def _event_signature(event):
    event_type = str(event.get("event_type") or "")
    payload = event.get("payload") if isinstance(event.get("payload"), dict) else {}
    text_sig = ""
    if event_type == "observe_output":
        text_sig = str(payload.get("text") or "")
    return (
        event_type,
        str(event.get("outcome") or ""),
        str(payload.get("step_name") or ""),
        str(payload.get("stream") or ""),
        str(payload.get("exit_code") or ""),
        text_sig,
    )


def _first_divergence_index(events_a, events_b):
    limit = min(len(events_a), len(events_b))
    for idx in range(limit):
        if _event_signature(events_a[idx]) != _event_signature(events_b[idx]):
            return idx
    if len(events_a) != len(events_b):
        return limit
    return -1


def _build_run_diff(record_a, record_b, events_a, events_b, tail_lines=20):
    outputs_a = _collect_output_lines(events_a)
    outputs_b = _collect_output_lines(events_b)
    divergence = _first_divergence_index(events_a, events_b)
    ta = max(1, int(tail_lines or 20))
    summary_a = {
        "run_id": str(record_a.get("run_id") or ""),
        "status": _record_effective_status(record_a),
        "duration_ms": int(record_a.get("duration_ms") or 0),
        "started_at_ms": int(record_a.get("started_at_ms") or 0),
    }
    summary_b = {
        "run_id": str(record_b.get("run_id") or ""),
        "status": _record_effective_status(record_b),
        "duration_ms": int(record_b.get("duration_ms") or 0),
        "started_at_ms": int(record_b.get("started_at_ms") or 0),
    }
    config_deltas = {}
    for key in ("command", "cwd", "trace_log_path"):
        av = str(record_a.get(key) or "")
        bv = str(record_b.get(key) or "")
        if av != bv:
            config_deltas[key] = {"a": av, "b": bv}
    execution = {
        "events_a": len(events_a),
        "events_b": len(events_b),
        "first_divergence_index": int(divergence),
    }
    output = {
        "stdout_count_a": len(outputs_a["stdout"]),
        "stdout_count_b": len(outputs_b["stdout"]),
        "stderr_count_a": len(outputs_a["stderr"]),
        "stderr_count_b": len(outputs_b["stderr"]),
        "stdout_tail_a": outputs_a["stdout"][-ta:],
        "stdout_tail_b": outputs_b["stdout"][-ta:],
        "stderr_tail_a": outputs_a["stderr"][-ta:],
        "stderr_tail_b": outputs_b["stderr"][-ta:],
    }
    if summary_a["status"] != summary_b["status"]:
        hint = "Run outcomes differ; inspect finish step and error/output tails first."
    elif divergence >= 0:
        hint = "Execution diverged; inspect event stream near first_divergence_index."
    elif config_deltas:
        hint = "Config changed with stable execution shape."
    else:
        hint = "Runs are closely aligned at inspected granularity."
    return {
        "run_a": summary_a,
        "run_b": summary_b,
        "config_deltas": config_deltas,
        "execution": execution,
        "output": output,
        "hint": hint,
    }


def _handle_trace_diff(args):
    _maybe_auto_reconcile()
    run_id_a = str(args.run_id_a or "").strip()
    run_id_b = str(args.run_id_b or "").strip()
    if not run_id_a or not run_id_b:
        raise SystemExit("run_id_a and run_id_b are required")
    record_a = _read_run_record(run_id_a)
    record_b = _read_run_record(run_id_b)
    if not record_a:
        raise SystemExit(f"run_id not found: {run_id_a}")
    if not record_b:
        raise SystemExit(f"run_id not found: {run_id_b}")
    limit = max(1, min(20000, int(args.event_limit or 2000)))
    tail_lines = max(1, min(200, int(args.tail_lines or 20)))
    events_a = _read_trace_events_for_run(run_id_a, limit=limit, trace_log_path=str(record_a.get("trace_log_path") or ""))
    events_b = _read_trace_events_for_run(run_id_b, limit=limit, trace_log_path=str(record_b.get("trace_log_path") or ""))
    diff = _build_run_diff(record_a, record_b, events_a, events_b, tail_lines=tail_lines)
    if args.json:
        print(json.dumps(diff, separators=(",", ":")))
        return 0
    print(f"run_a={diff['run_a']['run_id']} status={diff['run_a']['status']} duration_ms={diff['run_a']['duration_ms']}")
    print(f"run_b={diff['run_b']['run_id']} status={diff['run_b']['status']} duration_ms={diff['run_b']['duration_ms']}")
    if diff["config_deltas"]:
        print("config_deltas:")
        for key, item in diff["config_deltas"].items():
            print(f"  {key}:")
            print(f"    a={_shorten(item.get('a'), limit=120)}")
            print(f"    b={_shorten(item.get('b'), limit=120)}")
    else:
        print("config_deltas: none")
    exec_meta = diff["execution"]
    print(
        "execution:"
        f" events_a={exec_meta['events_a']} events_b={exec_meta['events_b']} first_divergence_index={exec_meta['first_divergence_index']}"
    )
    out = diff["output"]
    print(
        "output_counts:"
        f" stdout_a={out['stdout_count_a']} stdout_b={out['stdout_count_b']}"
        f" stderr_a={out['stderr_count_a']} stderr_b={out['stderr_count_b']}"
    )
    print(f"hint={diff['hint']}")
    return 0


def _find_recent_success_for_command(command_text, exclude_run_id):
    target = str(command_text or "").strip()
    if not target:
        return None
    exclude = str(exclude_run_id or "").strip()
    for record in _list_all_run_records():
        if str(record.get("run_id") or "").strip() == exclude:
            continue
        if _record_effective_status(record) != "success":
            continue
        if str(record.get("command") or "").strip() == target:
            return record
    return None


def _build_run_diagnosis(record, events, tail_lines=20):
    status = _record_effective_status(record)
    exit_code = int(record.get("exit_code") or 0)
    outputs = _collect_output_lines(events)
    stderr_lines = outputs["stderr"]
    stdout_lines = outputs["stdout"]
    t = max(1, int(tail_lines or 20))
    proxy_failed = any(
        str(event.get("event_type") or "") == "observe_proxy_response" and str(event.get("outcome") or "") == "failed"
        for event in events
    )
    timeout_seen = any(
        str(event.get("event_type") or "") == "observe_run_finished"
        and str((event.get("payload") or {}).get("error") or "") == "timeout_expired"
        for event in events
    )
    if status == "success":
        category = "success"
    elif timeout_seen or exit_code == 124:
        category = "timeout"
    elif proxy_failed:
        category = "proxy_error"
    elif exit_code != 0:
        category = "nonzero_exit"
    elif stderr_lines and len(stderr_lines) >= len(stdout_lines):
        category = "stderr_heavy"
    elif not stderr_lines and not stdout_lines:
        category = "no_output"
    else:
        category = "unknown"

    suggestions = []
    if category == "success":
        suggestions.append("Run is healthy; compare with alternate configurations using `hive trace diff`.")
    elif category == "timeout":
        suggestions.append("Replay with a higher timeout using `hive trace replay <run_id> --timeout-sec <n>`.")
    elif category == "proxy_error":
        suggestions.append("Verify upstream base URL and auth, then replay with `hive trace run --proxy --proxy-upstream ...`.")
    elif category == "nonzero_exit":
        suggestions.append("Inspect stderr tail and exit code, then replay after adjusting command/env.")
    elif category == "stderr_heavy":
        suggestions.append("Prioritize stderr output review and compare against a known-success run.")
    elif category == "no_output":
        suggestions.append("Confirm command emitted output and did not exit before logging.")
    else:
        suggestions.append("Inspect run with `hive trace show` and compare with `hive trace diff`.")

    baseline = _find_recent_success_for_command(record.get("command"), record.get("run_id"))
    baseline_run_id = str((baseline or {}).get("run_id") or "").strip()
    if baseline_run_id:
        suggestions.append(f"Compare against prior success: `hive trace diff {baseline_run_id} {record.get('run_id')}`.")

    return {
        "run_id": str(record.get("run_id") or ""),
        "status": status,
        "category": category,
        "exit_code": exit_code,
        "duration_ms": int(record.get("duration_ms") or 0),
        "event_count": len(events),
        "stderr_tail": stderr_lines[-t:],
        "stdout_tail": stdout_lines[-t:],
        "baseline_run_id": baseline_run_id or None,
        "recommendations": suggestions,
    }


def _handle_trace_diagnose(args):
    _maybe_auto_reconcile()
    run_id = str(args.run_id or "").strip()
    if not run_id:
        raise SystemExit("run_id is required")
    record = _read_run_record(run_id)
    if not record:
        raise SystemExit(f"run_id not found: {run_id}")
    limit = max(1, min(20000, int(args.event_limit or 2000)))
    tail_lines = max(1, min(200, int(args.tail_lines or 20)))
    events = _read_trace_events_for_run(run_id, limit=limit, trace_log_path=str(record.get("trace_log_path") or ""))
    diagnosis = _build_run_diagnosis(record, events, tail_lines=tail_lines)
    if args.json:
        print(json.dumps(diagnosis, separators=(",", ":")))
        return 0
    print(
        f"run_id={diagnosis['run_id']} status={diagnosis['status']} category={diagnosis['category']} "
        f"exit_code={diagnosis['exit_code']} duration_ms={diagnosis['duration_ms']}"
    )
    if diagnosis["baseline_run_id"]:
        print(f"baseline={diagnosis['baseline_run_id']}")
    if diagnosis["stderr_tail"]:
        print("stderr_tail:")
        for line in diagnosis["stderr_tail"]:
            print(f"  {line}")
    if diagnosis["stdout_tail"]:
        print("stdout_tail:")
        for line in diagnosis["stdout_tail"]:
            print(f"  {line}")
    print("recommendations:")
    for item in diagnosis["recommendations"]:
        print(f"  - {item}")
    return 0


def _handle_trace_open(args):
    _maybe_auto_reconcile()
    run_id = str(args.run_id or "").strip()
    if not run_id:
        raise SystemExit("run_id is required")
    record = _read_run_record(run_id)
    if not record:
        raise SystemExit(f"run_id not found: {run_id}")
    base_url = str(args.ui_url or os.environ.get("HIVE_TRACE_UI_URL") or "http://localhost:5173").strip()
    url = _open_run_in_ui(base_url, run_id)
    print(f"opened={url}")
    return 0


def _handle_trace_archive(args):
    run_id = str(args.run_id or "").strip()
    if not run_id:
        raise SystemExit("run_id is required")
    record = _archive_run_record(run_id, archive=True, reason=args.reason, actor=args.actor)
    if not record:
        raise SystemExit(f"run_id not found: {run_id}")
    print(f"archived={run_id}")
    return 0


def _handle_trace_unarchive(args):
    run_id = str(args.run_id or "").strip()
    if not run_id:
        raise SystemExit("run_id is required")
    record = _archive_run_record(run_id, archive=False)
    if not record:
        raise SystemExit(f"run_id not found: {run_id}")
    print(f"unarchived={run_id}")
    return 0


def _handle_trace_prune(args):
    try:
        window_ms = _parse_age_window_ms(args.older_than)
    except ValueError as exc:
        raise SystemExit(str(exc))
    now = _now_ms()
    cutoff = now - window_ms
    all_records = _list_all_run_records()
    candidates = []
    for record in all_records:
        if not bool(args.include_active) and not _record_is_archived(record):
            continue
        ref_ms = _record_reference_time_ms(record)
        if ref_ms <= 0:
            continue
        if ref_ms <= cutoff:
            candidates.append(record)
    if not candidates:
        print("pruned_runs=0 dropped_events=0")
        return 0

    run_ids_by_log = {}
    removed = 0
    for record in candidates:
        run_id = str(record.get("run_id") or "").strip()
        if not run_id:
            continue
        try:
            _run_file(run_id).unlink(missing_ok=True)
            removed += 1
        except Exception:
            continue
        trace_log = str(record.get("trace_log_path") or "").strip()
        if trace_log:
            run_ids_by_log.setdefault(trace_log, set()).add(run_id)
    dropped = {"dropped_events": 0, "touched_files": 0}
    if bool(args.drop_events):
        dropped = _drop_events_for_runs(run_ids_by_log)
    print(
        f"pruned_runs={removed} dropped_events={int(dropped['dropped_events'])} touched_files={int(dropped['touched_files'])}"
    )
    return 0


def _handle_trace_reconcile(args):
    if not bool(getattr(hive_config, "TRACE_RECONCILE_ENABLED", True)):
        summary = {
            "checked_running": 0,
            "reconciled_runs": 0,
            "reconciled_run_ids": [],
            "stale_after_ms": 0,
            "enabled": False,
        }
        if args.json:
            print(json.dumps(summary, separators=(",", ":")))
        else:
            print("reconcile disabled by config")
        return 0
    stale_raw = str(args.stale_after or "").strip()
    if stale_raw:
        try:
            stale_ms = _parse_duration_window_ms(stale_raw, default_unit="s")
        except ValueError as exc:
            raise SystemExit(str(exc))
    else:
        stale_sec = int(getattr(hive_config, "TRACE_RECONCILE_STALE_SEC", 300) or 300)
        stale_ms = max(1, stale_sec) * 1000
    summary = _reconcile_stale_running_runs(stale_after_ms=stale_ms, emit_events=True)
    summary["enabled"] = True
    if args.json:
        print(json.dumps(summary, separators=(",", ":")))
    else:
        print(
            f"checked_running={summary['checked_running']} reconciled_runs={summary['reconciled_runs']} "
            f"stale_after_ms={summary['stale_after_ms']}"
        )
        if summary["reconciled_run_ids"]:
            print("reconciled_run_ids:")
            for run_id in summary["reconciled_run_ids"]:
                print(f"  {run_id}")
    return 0


def _check_python_runtime():
    major, minor = sys.version_info[:2]
    ok = (major, minor) >= (3, 10)
    msg = f"Python {major}.{minor}"
    if not ok:
        msg += " (requires >= 3.10)"
    return ok, msg


def _check_trace_home():
    try:
        root = _trace_home()
        root.mkdir(parents=True, exist_ok=True)
        probe = root / ".write_probe"
        probe.write_text("ok", encoding="utf-8")
        probe.unlink(missing_ok=True)
        return True, f"trace home writable: {root}"
    except Exception as exc:
        return False, f"trace home not writable: {_shorten(exc, limit=200)}"


def _check_ui_reachable(ui_url):
    url = str(ui_url or "").strip() or "http://localhost:5173"
    host = "localhost"
    port = 5173
    try:
        stripped = url.split("://", 1)[-1]
        host_port = stripped.split("/", 1)[0]
        if ":" in host_port:
            host, raw_port = host_port.rsplit(":", 1)
            port = int(raw_port)
        else:
            host = host_port
            port = 80
    except Exception:
        host, port = "localhost", 5173
    try:
        with socket.create_connection((host, port), timeout=1.2):
            return True, f"ui reachable: {host}:{port}"
    except Exception:
        return False, f"ui unreachable: {host}:{port}"


def _handle_doctor(args):
    checks = [
        ("python", *_check_python_runtime()),
        ("trace_home", *_check_trace_home()),
        ("ui", *_check_ui_reachable(args.ui_url)),
    ]
    all_ok = True
    for name, ok, message in checks:
        status = "OK" if ok else "WARN"
        print(f"[{status}] {name}: {message}")
        if not ok and name in {"python", "trace_home"}:
            all_ok = False
    return 0 if all_ok else 2


def _handle_quickstart(args):
    ui_url = _resolve_ui_url_for_command(args)
    result = run_command_observed(
        ["python", "-c", "print('hive-trace quickstart ok')"],
        run_name="hive-quickstart",
        agent_id="observer.quickstart",
        open_ui_url=ui_url,
    )
    print("quickstart_complete=true")
    print(f"run_id={result['run_id']}")
    print("next=hive trace ls")
    print(f"next=hive trace show {result['run_id']}")
    if ui_url:
        print(f"next=hive trace open {result['run_id']} --ui-url {ui_url}")
    return int(result["exit_code"])


def main(argv=None):
    raw_argv = list(argv if argv is not None else sys.argv[1:])
    if raw_argv:
        # Legacy compatibility: `hive run -- ...` => `hive trace run -- ...`
        if raw_argv[0] == "run":
            raw_argv = ["trace", "run", *raw_argv[1:]]
        # Legacy compatibility: `hive trace-events ...`
        if raw_argv and raw_argv[0] == "trace-events":
            return _handle_legacy_trace_events_argv(raw_argv[1:])

    parser = _build_parser()
    args = parser.parse_args(raw_argv)

    if args.command == "quickstart":
        return _handle_quickstart(args)
    if args.command == "doctor":
        return _handle_doctor(args)

    if args.command == "trace":
        if args.trace_command == "run":
            return _handle_trace_run(args)
        if args.trace_command == "replay":
            return _handle_trace_replay(args)
        if args.trace_command == "ls":
            return _handle_trace_ls(args)
        if args.trace_command == "show":
            return _handle_trace_show(args)
        if args.trace_command == "tail":
            return _handle_trace_tail(args)
        if args.trace_command == "export":
            return _handle_trace_export(args)
        if args.trace_command == "import":
            return _handle_trace_import(args)
        if args.trace_command == "summary":
            return _handle_trace_summary(args)
        if args.trace_command == "diff":
            return _handle_trace_diff(args)
        if args.trace_command == "diagnose":
            return _handle_trace_diagnose(args)
        if args.trace_command == "open":
            return _handle_trace_open(args)
        if args.trace_command == "archive":
            return _handle_trace_archive(args)
        if args.trace_command == "unarchive":
            return _handle_trace_unarchive(args)
        if args.trace_command == "prune":
            return _handle_trace_prune(args)
        if args.trace_command == "reconcile":
            return _handle_trace_reconcile(args)
        parser.print_help()
        return 1

    parser.print_help()
    return 1


if __name__ == "__main__":
    raise SystemExit(main())
    run_id = _build_run_id("observe-run")
