// A rollout, live: where it is (task, sandbox, agent, grading), the agent's trajectory as the capture proxy recorded // it, and the reward when it lands. Laid out like the MiMo explorer's run page. import { $, api, esc, md, dur, ago, statusPill, toast, LIVE_STATUSES, rewardClass, rewardText, sk, emptyState, visibilityBadge, confetti } from "./util.js"; import { icon } from "./icons.js"; import { refreshActive } from "./session.js"; const PHASES = [["task", "Task"], ["sandbox", "Sandbox"], ["agent", "Agent"], ["grading", "Grading"], ["done", "Done"]]; const ORDER = { queued: -1, task: 0, sandbox: 1, agent: 2, grading: 3, done: 4 }; const TOOL_ICON = { bash: "terminal", shell: "terminal", execute_bash: "terminal", read: "file", read_file: "file", write: "pencil", write_file: "pencil", edit: "pencil", str_replace_editor: "pencil", grep: "search", glob: "search", todowrite: "check", list: "list" }; const enc = (s) => s.split("/").map(encodeURIComponent).join("/"); // a run's task page: its path is the task's ref in its environment (rollouts from before refs named a row apart) const refOf = (r) => (r.row ? `${r.row.config === "default" ? "" : `${r.row.config}/`}${r.row.split}/${r.row.i}` : r.path); const taskHref = (r) => `/t/${enc(r.dataset)}/${enc(refOf(r))}`; let timer = null, alive = true; let other = null; // the MiMo harness's own run page, for its rollouts (run-mimo.js) export function unmount() { alive = false; clearTimeout(timer); other?.unmount(); other = null; } const isLive = (r) => r && LIVE_STATUSES.includes(r.status); const served = (r) => (!r.provider ? "the HF router" : r.provider === "fastest" ? "the fastest provider" : r.provider === "cheapest" ? "the cheapest provider" : r.provider); function skeleton() { return `
${sk.line(18)}
${sk.line(22, 22)}${sk.line(55, 24)}${sk.line(45)}
${PHASES.map(() => `
${sk.box(20, "width:20px;border-radius:50%")}${sk.line(60)}
`).join("")}
${[70, 90, 55, 85].map((w) => sk.box(30, `width:${w}%;border-radius:10px`)).join("")}
${sk.card(sk.line(30, 14) + sk.line(25, 40) + sk.lines(90, 70))}
`; } export async function mount(el, { id }) { alive = true; el.innerHTML = skeleton(); const st = { id, run: null, traj: [], follow: true, shown: 0 }; let first; try { first = await api(`/api/runs/${encodeURIComponent(id)}`); } catch (e) { if (!alive) return; el.innerHTML = `
${emptyState("search", "Rollout not found", e.status === 404 ? "It may be private, or the link is wrong." : esc(e.message), `${icon("arrowLeft")}Your rollouts`)}
`; return; } if (!alive) return; if (["mimo", "nemo-gym"].includes(first.run.runner)) { other = await import("./run-mimo.js"); if (alive) other.mount(el, id, first); return; } el.innerHTML = `

What the agent did

    `; $("#rp-follow", el).addEventListener("change", (e) => (st.follow = e.target.checked)); $("#rp-expand", el).addEventListener("click", (e) => { const open = e.target.textContent === "Expand all"; el.querySelectorAll("#rp-tl details").forEach((d) => (d.open = open)); e.target.textContent = open ? "Collapse all" : "Expand all"; }); el.addEventListener("click", async (e) => { if (e.target.closest("#rp-stop")) { e.target.closest("#rp-stop").disabled = true; try { await api(`/api/runs/${encodeURIComponent(id)}/cancel`, { method: "POST" }); toast("Stopping: the sandbox is shut down"); } catch (err) { toast(err.message); } } const v = e.target.closest("[data-vis]"); if (v) { try { const r = await api(`/api/runs/${encodeURIComponent(id)}/visibility`, { method: "POST", body: { visibility: v.dataset.vis } }); st.run = r.run; renderHead(el, st); if (v.dataset.vis === "public") { const b = v.getBoundingClientRect(); confetti(b.left + b.width / 2, b.top); toast("Public: thank you for sharing"); } else toast("Private: only you can see this rollout"); } catch (err) { toast(err.message); } } }); apply(el, st, first); const poll = async () => { if (!alive) return; try { apply(el, st, await api(`/api/runs/${encodeURIComponent(id)}`)); } catch { /* next tick */ } if (alive && isLive(st.run)) timer = setTimeout(poll, 2000); else refreshActive(); }; if (isLive(st.run)) timer = setTimeout(poll, 2000); } function apply(el, st, d) { st.run = d.run; const grew = d.trajectory.length !== st.traj.length || JSON.stringify(d.trajectory.at(-1)) !== JSON.stringify(st.traj.at(-1)); st.traj = d.trajectory; renderHead(el, st); renderPhases(el, st); if (grew) renderTimeline(el, st); renderTail(el, st); renderGrade(el, st); renderCost(el, st); renderMeta(el, st); } function renderHead(el, st) { const r = st.run, owner = r.is_owner; const end = r.finished_at || Date.now() / 1000, start = r.started_at || r.created_at; const vis = owner && !r.restricted ? `${["public", "private"].map((v) => ``).join("")}` : visibilityBadge(r.visibility); const model = r.endpoint ? r.model : (r.model || "").split("/")[1] || r.model; $("#rp-head", el).innerHTML = `
    ${statusPill(r.status)}${r.reward != null ? `${rewardText(r.reward)}` : ""} ${owner ? "" : 'shared anonymously'} ${icon("clock", 13)}${dur(end - start)} · started ${ago(r.created_at)}

    ${esc(r.title)}

    ${icon("database", 14)}${esc(r.dataset)} ${icon("cpu", 14)}${esc(model)}${r.endpoint ? " via your endpoint" : ` via ${esc(served(r))}`} ${icon("terminal", 14)}${esc(r.harness)}${icon("box", 14)}HF Sandbox ${esc(r.flavor || "")}
    ${vis}${icon("file", 13)}View task ${owner && isLive(r) ? `` : ""}
    ${r.error ? `
    ${icon("alert")}${esc(r.error)}
    ` : ""} ${r.note ? `
    ${icon("clock")}${esc(r.note)}. The task's tests graded what it had done by then.
    ` : ""}`; } function renderPhases(el, st) { const r = st.run; const failed = ["failed", "cancelled", "interrupted"].includes(r.status); const at = r.status === "done" ? 4 : ORDER[r.phase] ?? -1; const pt = r.phase_timings || {}; const took = { sandbox: pt.environment_setup, agent: (pt.agent_setup || 0) + (pt.agent_execution || 0) || null, grading: pt.verifier }; $("#rp-phases", el).innerHTML = PHASES.map(([k, label], i) => { const state = i < at || (r.status === "done" && i === at) ? "ok" : i === at ? (failed ? "bad" : "on") : "off"; const mk = state === "ok" ? icon("check", 12) : state === "bad" ? icon("x", 12) : ""; return `
    ${mk}${label}${took[k] ? dur(took[k]) : ""}
    `; }).join(""); } // ── the trajectory: OpenAI-style messages as timeline rows ─────────────────── const row = (cls, ic, body) => `
  1. ${icon(ic, 14)}
    ${body}
  2. `; const summary = (inner) => `${icon("chevronRight", 13, "chev")}${inner}`; const textOf = (c) => (typeof c === "string" ? c : Array.isArray(c) ? c.map((p) => (typeof p === "string" ? p : p?.text || "")).join("\n") : c == null ? "" : JSON.stringify(c)); function parseArgs(a) { if (typeof a !== "string") return a || {}; try { return JSON.parse(a); } catch { return { arguments: a }; } } function rows(messages) { const results = {}; messages.forEach((m) => { if (m.role === "tool" && m.tool_call_id) results[m.tool_call_id] = m; }); let users = 0, steps = 0; const out = []; for (const m of messages) { const text = textOf(m.content); if (m.role === "system") { out.push(row("ev-prompt", "doc", `
    ${summary(`System prompt${text.length.toLocaleString()} characters, from the agent`)}
    ${esc(text)}
    `)); } else if (m.role === "user") { users++; out.push(row("ev-prompt", "doc", `
    ${summary(`${users === 1 ? "Instructions to the model" : "Message to the model"}${text.length.toLocaleString()} characters`)}
    ${esc(text)}
    `)); } else if (m.role === "assistant") { const thinking = m.reasoning_content || m.reasoning || ""; if (thinking) out.push(row("ev-think", "brain", `
    ${summary(`Thinking${esc(String(thinking).replace(/\s+/g, " ").slice(0, 220))}`)}
    ${esc(thinking)}
    `)); if (text.trim()) { out.push(row("ev-text", "message", `
    ${md(text)}
    `)); steps++; } for (const c of m.tool_calls || []) { const fn = c.function || {}, args = parseArgs(fn.arguments), res = results[c.id]; const head = args.command || args.cmd || args.keystrokes || args.filePath || args.path || args.pattern || Object.values(args).map(String).join(" "); const output = res ? textOf(res.content) : ""; out.push(row(`ev-tool`, TOOL_ICON[fn.name] || "terminal", `
    ${summary(`${esc(fn.name || "tool")}${esc(String(head).split("\n")[0].slice(0, 200))}`)}
    Input
    ${esc(JSON.stringify(args, null, 2))}
    Output
    ${esc(output || (res ? "(empty)" : "(waiting)"))}
    `)); steps++; } } else if (m.role === "tool" && !m.tool_call_id) { out.push(row("ev-log", "terminal", `
    ${summary(`Tool output${esc(text.split("\n")[0].slice(0, 160))}`)}
    ${esc(text)}
    `)); } } return { html: out.join(""), steps }; } function renderTimeline(el, st) { const tl = $("#rp-tl", el); const open = new Set([...tl.querySelectorAll("details")].map((d, i) => (d.open ? i : -1)).filter((i) => i >= 0)); const { html, steps } = rows(st.traj); tl.innerHTML = html; tl.querySelectorAll("details").forEach((d, i) => { if (open.has(i)) d.open = true; }); $("#rp-n", el).textContent = steps ? `${steps} step${steps === 1 ? "" : "s"}` : ""; if (st.follow && isLive(st.run)) tl.lastElementChild?.scrollIntoView({ block: "nearest", behavior: "smooth" }); } function renderTail(el, st) { const r = st.run, t = $("#rp-tail", el); const msg = { queued: "Waiting to start…", task: "Fetching the task…", sandbox: "Starting the HF Sandbox from the task's image and installing the agent…", agent: "The agent is working…" }[r.phase]; t.innerHTML = isLive(r) && msg ? `
    1. ${esc(msg)}
    ` : !st.traj.length && !isLive(r) ? `

    No model calls were recorded.

    ` : ""; } function renderGrade(el, st) { const r = st.run, box = $("#rp-grade", el); const h = `

    ${icon("scale", 14)}Grade

    ${r.reward_key ? `reward key ${esc(r.reward_key)}` : ""}
    `; if (r.reward == null) { box.innerHTML = `${h}

    ${isLive(r) ? `Appears here once the agent finishes and the task's ${r.judge ? `grader has run (it asks ${esc(r.judge)}, through a relay that only this rollout's grading can use)` : "tests have run"}.` : "This rollout was not graded."}

    `; return; } const cls = rewardClass(r.reward); const named = Object.entries(r.rewards || {}); box.innerHTML = `${h}
    ${rewardText(r.reward)}reward, from the task's ${r.judge ? `grader, with ${esc(r.judge)} as its judge${r.judge_calls != null ? ` (${r.judge_calls} call${r.judge_calls === 1 ? "" : "s"})` : ""}` : "tests"}
    ${named.length > 1 ? `` : ""} ${(r.findings || []).length ? `
    ${icon("chevronRight", 14, "chev")}Capture notes (${r.findings.length})
    ` : ""}
    `; } function renderCost(el, st) { const r = st.run, c = r.cost || {}; const wall = r.wall_s ?? ((r.finished_at || Date.now() / 1000) - (r.started_at || r.created_at)); $("#rp-cost", el).innerHTML = `

    ${icon("coins", 14)}Cost

    ${isLive(r) ? ` live` : ""}
    Sandbox time
    ${dur(wall)}${c.sandbox != null ? ` ${c.sandbox < 0.01 ? "under $0.01" : `about $${c.sandbox.toFixed(2)}`}` : ""}
    Model calls
    ${r.n_turns ?? 0}
    ${r.judge ? `
    Judge calls
    ${r.judge_calls ?? (isLive(r) ? "after grading" : "–")}
    ` : ""}

    ${r.endpoint ? "Tokens are billed by your endpoint; the sandbox by Hugging Face." : `Billed to ${r.is_owner ? "your" : "the runner's"} Hugging Face account: the sandbox by the hour, tokens at the provider's price.`}

    `; } function renderMeta(el, st) { const r = st.run, p = r.params || {}; const row2 = (k, v) => (v == null || v === "" ? "" : `
    ${k}
    ${v}
    `); const code = (v) => `${esc(v)}`; $("#rp-meta", el).innerHTML = `

    ${icon("info", 14)}Run details

    ${row2("Model", code(r.model || ""))} ${row2("Served by", r.endpoint ? "an OpenAI-compatible endpoint" : esc(served(r)))} ${row2("Agent", esc(r.harness))} ${row2("Judge", r.judge ? code(r.judge) : "")} ${row2("Step cap", esc(p.steps || "none"))} ${row2("Time limit", p.timeout_min ? esc(`${p.timeout_min} min`) : "the task's")} ${row2("Sandbox", esc(`HF Sandbox, ${r.flavor || "cpu-basic"}`))} ${row2("Image", r.image ? code(r.image) : "")} ${row2("Task", code(`${r.dataset}/${r.path}`))} ${row2("Runner", "OpenEnv's Harbor runner and capture proxy")} ${row2("Rollout", code(r.id))}
    `; }