import os os.environ.setdefault("HF_HOME", "/tmp/.cache/huggingface") os.environ.setdefault("HF_MODULES_CACHE", "/tmp/hf_modules") os.environ.setdefault("MPLCONFIGDIR", "/tmp/matplotlib") os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False") os.environ.setdefault("GRADIO_SSR_MODE", "false") if not os.environ.get("HF_TOKEN"): for _token_key in ("HUGGING_FACE_HUB_TOKEN", "HUGGINGFACE_HUB_TOKEN"): if os.environ.get(_token_key): os.environ["HF_TOKEN"] = os.environ[_token_key] break print("codex-traces startup: env configured", flush=True) import html import json import math import re from datetime import datetime, timezone from functools import lru_cache from pathlib import Path from typing import Any import gradio as gr from huggingface_hub import hf_hub_download, list_repo_files print("codex-traces startup: imports complete", flush=True) APP_TITLE = "Codex Traces" APP_REVISION = "parser-v2" DATASET_REPO = "Mike0021/codex-sessions" REPO_TYPE = "dataset" FILE_SUFFIX = "_rollout.jsonl" SESSION_PATH_PREFIX = "sessions/rollout-" SESSION_PATH_SUFFIX = ".jsonl" PAGE_SIZE = 120 MAX_OUTPUT_CHARS = 22000 MAX_MESSAGE_CHARS = 30000 KNOWN_SESSIONS = ( "agents-a1-demo", "ai-agent-soccer", "anime-soccer-generator", "asasr-space", "codex-traces-viewer", "craft-agents-oss", "deepspec-space", "edit-anything", "fractal-pi-extension", "gaussian-splat-demo", "hf-history-article", "hf-motion-video", "horus-hiero-space", "locate-anything-space", "ltx-3dreal-space", "microworld-space", "olmoearth-demo", "paris-13-landing", "pulpie-demo", "pulpie-gguf", "pulpie-mlx", "pulpie-onnx", "pulpie-web-demo", "qwen3-asr-space", "rampart-demo", "sync-lora-space", "tabfm-arena", ) ANSI_RE = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]") TIMESTAMP_KEYS = ( "timestamp", "time", "created_at", "updated_at", "started_at", "completed_at", ) def _hub_token() -> str | None: return ( os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN") or os.environ.get("HUGGINGFACE_HUB_TOKEN") or None ) def _display_name(file_name: str) -> str: if file_name.startswith(SESSION_PATH_PREFIX) and file_name.endswith(SESSION_PATH_SUFFIX): return file_name[len(SESSION_PATH_PREFIX) : -len(SESSION_PATH_SUFFIX)] return file_name[: -len(FILE_SUFFIX)] if file_name.endswith(FILE_SUFFIX) else file_name def _is_rollout_file(file_name: str) -> bool: return ( file_name.startswith(SESSION_PATH_PREFIX) and file_name.endswith(SESSION_PATH_SUFFIX) ) or file_name.endswith(FILE_SUFFIX) def _file_candidates(session_name: str | None) -> list[str]: if not session_name: return [] name = str(session_name).strip() if name.endswith(".jsonl"): return [name] candidates = [ f"{SESSION_PATH_PREFIX}{name}{SESSION_PATH_SUFFIX}", f"{name}{FILE_SUFFIX}", ] return list(dict.fromkeys(candidates)) def _fmt_number(value: int | None) -> str: return f"{int(value or 0):,}" def _fmt_bytes(value: int | None) -> str: value = int(value or 0) units = ("B", "KB", "MB", "GB") size = float(value) for unit in units: if size < 1024 or unit == units[-1]: return f"{size:.1f} {unit}" if unit != "B" else f"{value} B" size /= 1024 return f"{value} B" def _strip_ansi(text: Any) -> str: return ANSI_RE.sub("", str(text or "")) def _escape(text: Any) -> str: return html.escape(str(text or ""), quote=True) def _limit_text(text: str, limit: int, label: str) -> tuple[str, str]: if len(text) <= limit: return text, "" hidden = len(text) - limit note = f"\n\n[{label} truncated by {hidden:,} characters for browser responsiveness.]" return text[:limit] + note, f" truncated {hidden:,} chars" def _parse_time(value: Any) -> datetime | None: if value is None: return None if isinstance(value, (int, float)): seconds = float(value) / 1000 if value > 10_000_000_000 else float(value) try: return datetime.fromtimestamp(seconds, tz=timezone.utc) except (OSError, OverflowError, ValueError): return None if isinstance(value, str): raw = value.strip() if not raw: return None try: if raw.endswith("Z"): raw = raw[:-1] + "+00:00" parsed = datetime.fromisoformat(raw) return parsed if parsed.tzinfo else parsed.replace(tzinfo=timezone.utc) except ValueError: return None return None def _duration_label(timestamps: list[datetime]) -> str: if len(timestamps) < 2: return "not recorded" delta = max(timestamps) - min(timestamps) seconds = max(0, int(delta.total_seconds())) if seconds < 60: return f"{seconds}s" minutes, sec = divmod(seconds, 60) if minutes < 60: return f"{minutes}m {sec}s" hours, minutes = divmod(minutes, 60) return f"{hours}h {minutes}m" @lru_cache(maxsize=1) def _list_sessions_cached() -> tuple[tuple[str, ...], str]: try: files = list_repo_files(DATASET_REPO, repo_type=REPO_TYPE, token=_hub_token()) except Exception as exc: return (), f"{type(exc).__name__}: {exc}" sessions = tuple(sorted({_display_name(f) for f in files if _is_rollout_file(f)})) if not sessions: return (), "No *_rollout.jsonl files were found in the dataset." return sessions, "" def _session_choices(live: bool = True) -> list[str]: if not live: return list(KNOWN_SESSIONS) sessions, _ = _list_sessions_cached() return list(sessions or KNOWN_SESSIONS) def _default_session() -> str | None: choices = _session_choices(live=False) return choices[0] if choices else None def _download_session(file_name: str) -> Path: return Path( hf_hub_download( repo_id=DATASET_REPO, filename=file_name, repo_type=REPO_TYPE, token=_hub_token(), ) ) def _command_status(item: dict[str, Any]) -> tuple[str, str]: status = str(item.get("status") or "").lower() exit_code = item.get("exit_code") if status in {"failed", "error"} or (exit_code not in (None, 0)): return "failed", "fail" if status in {"completed", "succeeded", "success"}: return "success", "success" if status in {"in_progress", "running"}: return "running", "running" return status or "unknown", "neutral" def _usage_total(usage: dict[str, Any]) -> int: return int(usage.get("input_tokens") or 0) + int(usage.get("output_tokens") or 0) def _collect_timestamps(obj: dict[str, Any], timestamps: list[datetime]) -> None: for key in TIMESTAMP_KEYS: parsed = _parse_time(obj.get(key)) if parsed: timestamps.append(parsed) item = obj.get("item") if isinstance(item, dict): for key in TIMESTAMP_KEYS: parsed = _parse_time(item.get(key)) if parsed: timestamps.append(parsed) def _payload_command(payload: dict[str, Any]) -> str: name = str(payload.get("name") or "function_call") arguments = payload.get("arguments") if isinstance(arguments, dict): command = arguments.get("command") if command: return str(command) return f"{name}({json.dumps(arguments, ensure_ascii=True)})" if arguments: return f"{name}({arguments})" return name @lru_cache(maxsize=6) def _load_session(file_name: str) -> dict[str, Any]: path = _download_session(file_name) events: list[dict[str, Any]] = [] timestamps: list[datetime] = [] pending_commands: list[str] = [] command_ids: set[str] = set() failed_commands = 0 turn_completed = 0 turn_started = 0 parse_errors = 0 usage_summary = { "input_tokens": 0, "cached_input_tokens": 0, "output_tokens": 0, "reasoning_output_tokens": 0, } with path.open("r", encoding="utf-8", errors="replace") as handle: for line_no, raw_line in enumerate(handle, start=1): line = raw_line.strip() if not line.startswith("{"): parse_errors += 1 continue try: obj = json.loads(line) except json.JSONDecodeError: parse_errors += 1 continue obj_type = obj.get("type") payload = obj.get("payload") if isinstance(obj.get("payload"), dict) else {} payload_type = payload.get("type") item = obj.get("item") if isinstance(obj.get("item"), dict) else {} item_type = item.get("type") _collect_timestamps(obj, timestamps) if obj_type == "turn_context": turn_started += 1 continue if obj_type == "event_msg": if payload_type in {"agent_message", "user_message"}: text = str(payload.get("message") or "") is_user = payload_type == "user_message" events.append( { "kind": "user" if is_user else "agent", "role": "USER" if is_user else "AGENT", "line": line_no, "id": f"line-{line_no}", "text": text, "search": text, } ) continue if payload_type == "token_count": turn_completed += 1 usage = {key: int(payload.get(key) or 0) for key in usage_summary} for key, value in usage.items(): usage_summary[key] += value events.append( { "kind": "usage", "line": line_no, "usage": usage, "search": " ".join( [ "turn completed usage tokens", " ".join(f"{k} {v}" for k, v in usage.items()), ] ), } ) continue if payload_type == "item_completed" and payload.get("item_type") == "todo_list": todos = payload.get("todos") or payload.get("items") or [] if isinstance(todos, list): text = " ".join(str(todo.get("text") or "") for todo in todos if isinstance(todo, dict)) events.append( { "kind": "todo", "line": line_no, "id": f"line-{line_no}", "items": todos, "search": text, } ) continue if payload_type == "patch_apply_end": path_value = str(payload.get("path") or "patch") events.append( { "kind": "files", "line": line_no, "id": f"line-{line_no}", "changes": [{"kind": "patch", "path": path_value}], "status": payload.get("status") or "", "search": f"patch {path_value} {payload.get('status') or ''}", } ) continue if obj_type == "response_item": if payload_type == "function_call": pending_commands.append(_payload_command(payload)) continue if payload_type == "function_call_output": command = pending_commands.pop(0) if pending_commands else "function_call_output" output = _strip_ansi(payload.get("output") or "") exit_code = payload.get("exit_code") status = "failed" if exit_code not in (None, 0) else "completed" label, status_class = _command_status({"status": status, "exit_code": exit_code}) if status_class == "fail": failed_commands += 1 item_id = f"line-{line_no}" command_ids.add(item_id) events.append( { "kind": "command", "line": line_no, "id": item_id, "command": command, "output": output, "exit_code": exit_code, "status": label, "status_class": status_class, "search": " ".join([command, output, str(exit_code), label]), } ) continue if obj_type == "turn.started": turn_started += 1 continue if obj_type == "turn.completed": turn_completed += 1 usage = obj.get("usage") or {} if isinstance(usage, dict): for key in usage_summary: usage_summary[key] += int(usage.get(key) or 0) events.append( { "kind": "usage", "line": line_no, "usage": usage, "search": " ".join( [ "turn completed usage tokens", " ".join(f"{k} {v}" for k, v in usage.items()), ] ), } ) continue if obj_type not in {"item.completed", "item.updated"}: continue if item_type == "agent_message": text = str(item.get("text") or "") events.append( { "kind": "agent", "line": line_no, "id": item.get("id") or f"line-{line_no}", "text": text, "search": text, } ) continue if item_type == "command_execution": item_id = str(item.get("id") or f"line-{line_no}") if item_id not in command_ids: command_ids.add(item_id) label, status_class = _command_status(item) if status_class == "fail": failed_commands += 1 command = str(item.get("command") or "") output = _strip_ansi(item.get("aggregated_output") or "") exit_code = item.get("exit_code") events.append( { "kind": "command", "line": line_no, "id": item_id, "command": command, "output": output, "exit_code": exit_code, "status": label, "status_class": status_class, "search": " ".join([command, output, str(exit_code), label]), } ) continue if item_type == "todo_list": todos = item.get("items") or [] if isinstance(todos, list): text = " ".join(str(todo.get("text") or "") for todo in todos if isinstance(todo, dict)) events.append( { "kind": "todo", "line": line_no, "id": item.get("id") or f"line-{line_no}", "items": todos, "search": text, } ) continue if item_type == "file_change": changes = item.get("changes") or [] if isinstance(changes, list): text = " ".join( f"{change.get('kind', '')} {change.get('path', '')}" for change in changes if isinstance(change, dict) ) events.append( { "kind": "files", "line": line_no, "id": item.get("id") or f"line-{line_no}", "changes": changes, "status": item.get("status") or "", "search": text, } ) total_turns = turn_completed or turn_started return { "file_name": file_name, "path": str(path), "bytes": path.stat().st_size, "events": events, "summary": { "turns": total_turns, "commands": len(command_ids), "failed_commands": failed_commands, "duration": _duration_label(timestamps), "parse_errors": parse_errors, **usage_summary, "total_tokens": usage_summary["input_tokens"] + usage_summary["output_tokens"], }, } def _filter_events(events: list[dict[str, Any]], query: str | None) -> list[dict[str, Any]]: needle = (query or "").strip().lower() if not needle: return events terms = [part for part in needle.split() if part] return [ event for event in events if all(term in str(event.get("search", "")).lower() for term in terms) ] def _metric(label: str, value: str, tone: str = "") -> str: return ( f'
' f'{_escape(label)}' f'{_escape(value)}' "
" ) def _render_summary(data: dict[str, Any], matches: int, query: str) -> str: summary = data["summary"] file_label = _display_name(data["file_name"]) query_label = f"Filtered: {matches:,} matches" if query.strip() else f"{matches:,} visible events" failed = int(summary["failed_commands"]) return ( '
' '
' f'

{_escape(file_label)}

' f'

{_escape(data["file_name"])} · {_fmt_bytes(data["bytes"])} · {query_label}

' f'
private dataset · {_escape(APP_REVISION)}
' "
" '
' f'{_metric("Turns", _fmt_number(summary["turns"]))}' f'{_metric("Total tokens", _fmt_number(summary["total_tokens"]))}' f'{_metric("Input", _fmt_number(summary["input_tokens"]))}' f'{_metric("Output", _fmt_number(summary["output_tokens"]))}' f'{_metric("Reasoning", _fmt_number(summary["reasoning_output_tokens"]))}' f'{_metric("Duration", str(summary["duration"]))}' f'{_metric("Commands", _fmt_number(summary["commands"]))}' f'{_metric("Failed", _fmt_number(failed), "danger" if failed else "ok")}' "
" "
" ) def _render_agent(event: dict[str, Any]) -> str: text, note = _limit_text(str(event.get("text") or ""), MAX_MESSAGE_CHARS, "message") role = str(event.get("role") or "AGENT").upper() role_class = "user" if role == "USER" else "agent" return ( f'
' f'
{_escape(role)}
' f'
' f'
line {_fmt_number(event["line"])}{_escape(note)}
' f'
{_escape(text)}
' "
" "
" ) def _render_command(event: dict[str, Any]) -> str: output = str(event.get("output") or "") output_limited, note = _limit_text(output, MAX_OUTPUT_CHARS, "output") exit_code = event.get("exit_code") exit_label = "pending" if exit_code is None else str(exit_code) status = str(event.get("status") or "unknown") status_class = str(event.get("status_class") or "neutral") output_count = f"{len(output):,} chars" return ( '
' '
CMD
' '
' '
' f'{_escape(status)}' f'exit {_escape(exit_label)}' f'line {_fmt_number(event["line"])}' "
" f'
{_escape(event.get("command") or "")}
' f'
Command output · {output_count}{_escape(note)}' f'
{_escape(output_limited) if output_limited else "No output."}
' "
" "
" "
" ) def _render_usage(event: dict[str, Any]) -> str: usage = event.get("usage") or {} input_tokens = int(usage.get("input_tokens") or 0) cached = int(usage.get("cached_input_tokens") or 0) output = int(usage.get("output_tokens") or 0) reasoning = int(usage.get("reasoning_output_tokens") or 0) total = input_tokens + output return ( '
' '
TURN
' '
' f'Total {_fmt_number(total)}' f'Input {_fmt_number(input_tokens)}' f'Cached {_fmt_number(cached)}' f'Output {_fmt_number(output)}' f'Reasoning {_fmt_number(reasoning)}' f'line {_fmt_number(event["line"])}' "
" "
" ) def _render_todo(event: dict[str, Any]) -> str: items = event.get("items") or [] rows = [] for todo in items: if not isinstance(todo, dict): continue done = bool(todo.get("completed")) rows.append( '
  • ' f'{"✓" if done else "◯"}' f'{_escape(todo.get("text") or "")}' "
  • " ) return ( '
    ' '
    TODO
    ' '
    ' f'
    line {_fmt_number(event["line"])}
    ' f'' "
    " "
    " ) def _render_files(event: dict[str, Any]) -> str: changes = event.get("changes") or [] rows = [] for change in changes: if not isinstance(change, dict): continue rows.append( '
  • ' f'{_escape(change.get("kind") or "change")}' f'{_escape(change.get("path") or "")}' "
  • " ) return ( '
    ' '
    FILE
    ' '
    ' f'
    line {_fmt_number(event["line"])} · {_escape(event.get("status") or "")}
    ' f'' "
    " "
    " ) def _render_event(event: dict[str, Any]) -> str: kind = event.get("kind") if kind in {"agent", "user"}: return _render_agent(event) if kind == "command": return _render_command(event) if kind == "usage": return _render_usage(event) if kind == "todo": return _render_todo(event) if kind == "files": return _render_files(event) return "" def _render_transcript(events: list[dict[str, Any]], page: int, total_pages: int) -> str: if not events: return ( '
    ' "

    No matching transcript events

    " "

    Try a broader search term or choose another session.

    " "
    " ) start = (page - 1) * PAGE_SIZE visible = events[start : start + PAGE_SIZE] rendered = "".join(_render_event(event) for event in visible) return ( '
    ' f'
    Page {page:,} of {total_pages:,}
    ' f"{rendered}" "
    " ) def _render_error(title: str, details: str) -> tuple[str, str, str, int]: return ( '
    ' f"

    {_escape(title)}

    " f"

    {_escape(details)}

    " "
    ", '

    Session unavailable

    ', "No session loaded.", 1, ) def _initial_view(choices: list[str]) -> tuple[str, str, str, int]: choices_label = f"{len(choices):,} session files available" if choices else "No session files available" summary = ( '
    ' '
    ' "

    Select a session

    " f"

    {_escape(choices_label)} from {DATASET_REPO}

    " f'
    private dataset · {_escape(APP_REVISION)}
    ' "
    " '
    ' f'{_metric("Startup mode", "metadata only")}' f'{_metric("Files listed", _fmt_number(len(choices)))}' f'{_metric("Downloads", "on demand")}' f'{_metric("Page size", _fmt_number(PAGE_SIZE))}' "
    " "
    " ) transcript = ( '
    ' "

    No session loaded yet

    " "

    Choose a session in the sidebar and select Load session. Files are downloaded lazily.

    " "
    " ) return summary, transcript, "No session file downloaded yet.", 1 def render_session(session_name: str | None, query: str | None = "", page: int | float | None = 1) -> tuple[str, str, str, int]: choices = KNOWN_SESSIONS list_error = "" if not choices: return _render_error("Dataset file list unavailable", list_error or "HF_TOKEN is not configured.") session_name = session_name or choices[0] if session_name not in choices and _display_name(str(session_name)) in choices: session_name = _display_name(str(session_name)) candidates = _file_candidates(session_name) if not candidates: return _render_error("No session selected", "Choose a session from the dropdown.") last_error: Exception | None = None data = None for file_name in candidates: try: data = _load_session(file_name) break except Exception as exc: last_error = exc if data is None: details = f"{type(last_error).__name__}: {last_error}" if last_error else "No file candidates were available." return _render_error("Could not load session", details) query = query or "" filtered = _filter_events(data["events"], query) total_pages = max(1, math.ceil(len(filtered) / PAGE_SIZE)) try: page_int = int(page or 1) except (TypeError, ValueError): page_int = 1 page_int = min(max(1, page_int), total_pages) start = (page_int - 1) * PAGE_SIZE + 1 if filtered else 0 end = min(page_int * PAGE_SIZE, len(filtered)) page_info = ( f"Showing {start:,}-{end:,} of {len(filtered):,} matching events " f"({len(data['events']):,} total parsed)." ) if data["summary"]["parse_errors"]: page_info += f" Skipped {data['summary']['parse_errors']:,} non-JSON lines." summary = _render_summary(data, len(filtered), query) transcript = _render_transcript(filtered, page_int, total_pages) return summary, transcript, page_info, page_int def load_session(session_name: str | None) -> tuple[str, str, str, int, str]: summary, transcript, page_info, page = render_session(session_name, "", 1) return summary, transcript, page_info, page, "" def filter_session(session_name: str | None, query: str | None) -> tuple[str, str, str, int]: return render_session(session_name, query, 1) def next_page(session_name: str | None, query: str | None, page: int | float | None) -> tuple[str, str, str, int]: return render_session(session_name, query, int(page or 1) + 1) def previous_page(session_name: str | None, query: str | None, page: int | float | None) -> tuple[str, str, str, int]: return render_session(session_name, query, int(page or 1) - 1) def refresh_sessions() -> tuple[Any, str, str, str, int, str]: _list_sessions_cached.cache_clear() choices = _session_choices() value = None summary, transcript, page_info, page = _initial_view(choices) return gr.update(choices=choices, value=value), summary, transcript, page_info, page, "" CUSTOM_CSS = """ :root { --ct-bg: #0b0d10; --ct-panel: #11151a; --ct-panel-2: #151a21; --ct-line: #30363d; --ct-text: #e6edf3; --ct-muted: #8b949e; --ct-green: #3fb950; --ct-red: #f85149; --ct-yellow: #d29922; --ct-blue: #58a6ff; --ct-cyan: #39c5cf; --ct-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; --ct-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } html, body, .gradio-container { min-height: 100%; background: var(--ct-bg) !important; color: var(--ct-text) !important; font-family: var(--ct-sans) !important; } .gradio-container, .gradio-container .contain, .gradio-container main { max-width: none !important; width: 100% !important; padding: 0 !important; } #ct-app { min-height: 100vh; background: linear-gradient(180deg, rgba(88, 166, 255, 0.07), transparent 210px), var(--ct-bg); } .ct-shell { display: grid; grid-template-columns: minmax(260px, 330px) minmax(0, 1fr); gap: 0; min-height: 100vh; } .ct-sidebar { background: #0f1318; border-right: 1px solid var(--ct-line); padding: 22px 18px; position: sticky; top: 0; height: 100vh; overflow: auto; } .ct-brand { margin-bottom: 22px; } .ct-brand h1 { font-family: var(--ct-mono); font-size: 21px; line-height: 1.2; margin: 0 0 6px; color: var(--ct-text); letter-spacing: 0; } .ct-brand p { margin: 0; color: var(--ct-muted); font-size: 13px; line-height: 1.45; } .ct-sidebar .gradio-dropdown, .ct-sidebar .gradio-textbox, .ct-sidebar .gradio-button { font-family: var(--ct-mono) !important; } .ct-sidebar .block, .ct-sidebar .form, .ct-sidebar .wrap, .ct-sidebar .container, .ct-sidebar .secondary-wrap, .ct-sidebar .input-container, .ct-sidebar .block > div { background: transparent !important; border-color: var(--ct-line) !important; box-shadow: none !important; } .ct-sidebar .block { background: var(--ct-panel) !important; border: 1px solid var(--ct-line) !important; border-radius: 8px !important; } .ct-sidebar label, .ct-sidebar .block span { color: var(--ct-muted) !important; } .ct-sidebar input, .ct-sidebar textarea, .ct-sidebar .wrap, .ct-sidebar .container { background: var(--ct-panel) !important; color: var(--ct-text) !important; border-color: var(--ct-line) !important; } .ct-sidebar button { background: var(--ct-panel-2) !important; border: 1px solid var(--ct-line) !important; border-radius: 8px !important; color: var(--ct-text) !important; font-family: var(--ct-mono) !important; min-height: 42px; } .ct-sidebar button:hover { border-color: var(--ct-blue) !important; color: #ffffff !important; } .ct-sidebar button.primary, .ct-sidebar .primary button { background: #1f6feb !important; border-color: #388bfd !important; color: #ffffff !important; } .ct-main { padding: 22px; min-width: 0; } .summary-panel { background: var(--ct-panel); border: 1px solid var(--ct-line); border-radius: 8px; margin-bottom: 18px; padding: 18px; } .summary-topline { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 16px; } .summary-topline h1 { font-family: var(--ct-mono); font-size: 22px; margin: 0 0 6px; color: var(--ct-text); letter-spacing: 0; } .summary-topline p { margin: 0; color: var(--ct-muted); font-family: var(--ct-mono); font-size: 13px; } .repo-pill { border: 1px solid rgba(57, 197, 207, 0.45); color: var(--ct-cyan); border-radius: 999px; padding: 5px 10px; font-family: var(--ct-mono); font-size: 12px; white-space: nowrap; } .metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(135px, 1fr)); gap: 10px; } .metric { background: var(--ct-panel-2); border: 1px solid var(--ct-line); border-radius: 8px; padding: 10px 12px; min-height: 62px; } .metric-label { display: block; color: var(--ct-muted); font-size: 12px; margin-bottom: 6px; } .metric-value { display: block; color: var(--ct-text); font-family: var(--ct-mono); font-size: 17px; overflow-wrap: anywhere; } .metric.ok .metric-value { color: var(--ct-green); } .metric.danger .metric-value { color: var(--ct-red); } .transcript { display: flex; flex-direction: column; gap: 14px; } .page-marker { color: var(--ct-muted); font-family: var(--ct-mono); font-size: 12px; padding: 0 2px; } .event { display: grid; grid-template-columns: 58px minmax(0, 1fr); gap: 12px; align-items: start; } .event-rail { position: sticky; top: 18px; border: 1px solid var(--ct-line); border-radius: 8px; padding: 8px 0; text-align: center; font-family: var(--ct-mono); font-size: 11px; color: var(--ct-muted); background: #0f1318; } .agent-rail { color: var(--ct-blue); } .user-rail { color: var(--ct-cyan); } .command-rail { color: var(--ct-yellow); } .usage-rail { color: var(--ct-green); } .todo-rail { color: var(--ct-cyan); } .file-rail { color: #c9d1d9; } .bubble, .command-card, .usage-card, .todo-card, .file-card, .empty-state { border: 1px solid var(--ct-line); border-radius: 8px; background: var(--ct-panel); min-width: 0; } .agent-bubble { padding: 14px 15px; border-left: 3px solid var(--ct-blue); } .user-bubble { padding: 14px 15px; border-left: 3px solid var(--ct-cyan); background: #101820; } .event-meta { color: var(--ct-muted); font-family: var(--ct-mono); font-size: 12px; margin-bottom: 8px; } .message-text { white-space: pre-wrap; overflow-wrap: anywhere; color: var(--ct-text); line-height: 1.55; font-size: 14px; } .command-card { overflow: hidden; border-left: 3px solid var(--ct-yellow); } .command-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding: 12px 12px 0; } .status-badge, .exit-badge { font-family: var(--ct-mono); font-size: 12px; border-radius: 999px; border: 1px solid var(--ct-line); padding: 3px 8px; } .status-badge.success { color: var(--ct-green); border-color: rgba(63, 185, 80, .5); } .status-badge.fail { color: var(--ct-red); border-color: rgba(248, 81, 73, .55); } .status-badge.running { color: var(--ct-yellow); border-color: rgba(210, 153, 34, .55); } .status-badge.neutral { color: var(--ct-muted); } .exit-badge { color: var(--ct-muted); } .code { margin: 0; overflow: auto; font-family: var(--ct-mono); font-size: 12.5px; line-height: 1.55; color: #dbeafe; background: #070a0f; } .command-code { margin: 12px; padding: 12px; border: 1px solid #1f2937; border-radius: 8px; } .output-drawer { border-top: 1px solid var(--ct-line); } .output-drawer summary { cursor: pointer; color: var(--ct-muted); font-family: var(--ct-mono); font-size: 12px; padding: 10px 12px; } .output-code { max-height: 520px; padding: 12px; border-top: 1px solid #1f2937; } .usage-card { display: flex; flex-wrap: wrap; gap: 8px; padding: 12px; border-left: 3px solid var(--ct-green); } .usage-card span { font-family: var(--ct-mono); font-size: 12px; color: var(--ct-text); background: #0b1117; border: 1px solid var(--ct-line); border-radius: 999px; padding: 4px 9px; } .todo-card, .file-card { padding: 12px; } .todo-card { border-left: 3px solid var(--ct-cyan); } .file-card { border-left: 3px solid #c9d1d9; } .todo-card ul, .file-card ul { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; } .todo-row { display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: 8px; color: var(--ct-text); line-height: 1.45; } .todo-check { font-family: var(--ct-mono); color: var(--ct-muted); } .todo-check.done { color: var(--ct-green); } .file-card li { display: flex; gap: 10px; align-items: baseline; min-width: 0; } .file-kind { color: var(--ct-yellow); font-family: var(--ct-mono); font-size: 12px; min-width: 46px; } .file-card code { color: var(--ct-text); font-family: var(--ct-mono); overflow-wrap: anywhere; } .empty-state { padding: 32px; color: var(--ct-muted); } .empty-state h2 { color: var(--ct-text); margin: 0 0 8px; font-family: var(--ct-mono); font-size: 18px; letter-spacing: 0; } .error-panel { border-color: rgba(248, 81, 73, .6); } .ct-page-info { color: var(--ct-muted); font-family: var(--ct-mono); font-size: 12px; margin: 8px 0 12px; } .ct-page-info, .ct-page-info * { color: var(--ct-muted) !important; } .ct-pager { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } @media (max-width: 860px) { .ct-shell { grid-template-columns: 1fr; } .ct-sidebar { position: relative; height: auto; border-right: 0; border-bottom: 1px solid var(--ct-line); } .ct-main { padding: 14px; } .event { grid-template-columns: 1fr; } .event-rail { position: relative; top: 0; width: 58px; } .summary-topline { flex-direction: column; } } """ def build_app() -> gr.Blocks: print("codex-traces startup: building app", flush=True) choices = _session_choices(live=False) default = None initial_summary, initial_transcript, initial_page_info, initial_page = _initial_view(choices) theme = gr.themes.Base( primary_hue="blue", secondary_hue="cyan", neutral_hue="slate", radius_size="sm", ) with gr.Blocks(title=APP_TITLE, theme=theme, css=CUSTOM_CSS) as demo: with gr.Row(elem_id="ct-app", elem_classes=["ct-shell"]): with gr.Column(elem_classes=["ct-sidebar"], scale=0): gr.HTML( '
    ' "

    Codex Traces

    " "

    Private rollout.jsonl session viewer

    " "
    " ) session = gr.Dropdown( choices=choices, value=default, label="Session file", interactive=True, allow_custom_value=False, ) search = gr.Textbox( label="Search transcript", placeholder="Filter by keyword, command, output, token...", lines=1, max_lines=1, ) filter_button = gr.Button("Filter", variant="primary") load_button = gr.Button("Load session") refresh_button = gr.Button("Refresh file list") page_state = gr.State(initial_page) page_info = gr.Markdown(initial_page_info, elem_classes=["ct-page-info"]) with gr.Row(elem_classes=["ct-pager"]): prev_button = gr.Button("Previous") next_button = gr.Button("Next") with gr.Column(elem_classes=["ct-main"], scale=1): summary = gr.HTML(initial_summary) transcript = gr.HTML(initial_transcript) session.change( fn=load_session, inputs=session, outputs=[summary, transcript, page_info, page_state, search], api_name="load_session", show_progress="minimal", ) load_button.click( fn=load_session, inputs=session, outputs=[summary, transcript, page_info, page_state, search], api_name=False, show_progress="minimal", ) filter_button.click( fn=filter_session, inputs=[session, search], outputs=[summary, transcript, page_info, page_state], api_name="filter_session", show_progress="minimal", ) search.submit( fn=filter_session, inputs=[session, search], outputs=[summary, transcript, page_info, page_state], api_name=False, show_progress="minimal", ) prev_button.click( fn=previous_page, inputs=[session, search, page_state], outputs=[summary, transcript, page_info, page_state], api_name="previous_page", show_progress="minimal", ) next_button.click( fn=next_page, inputs=[session, search, page_state], outputs=[summary, transcript, page_info, page_state], api_name="next_page", show_progress="minimal", ) refresh_button.click( fn=refresh_sessions, inputs=None, outputs=[session, summary, transcript, page_info, page_state, search], api_name="refresh_sessions", show_progress="minimal", ) print("codex-traces startup: app built", flush=True) return demo demo = build_app() print("codex-traces startup: demo ready", flush=True) print("codex-traces startup: launching", flush=True) demo.launch( server_name="0.0.0.0", server_port=int(os.environ.get("PORT", "7860")), show_error=True, )