`;
}
export async function mount(el, id, first) {
alive = true;
const st = { id, after: 0, events: [], run: null, follow: true, steps: 0 };
if (!first) {
el.innerHTML = skeleton();
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;
}
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", onClick(st));
apply(el, st, first);
const poll = async () => {
if (!alive) return;
try { apply(el, st, await api(`/api/runs/${encodeURIComponent(id)}?after=${st.after}`)); } catch { /* retry next tick */ }
if (alive && isLive(st.run)) timer = setTimeout(poll, 1500);
else refreshActive();
};
if (isLive(st.run)) timer = setTimeout(poll, 1500);
tick = setInterval(() => { if (isLive(st.run)) { renderHead(el, st); renderTail(el, st); } }, 1000);
}
function apply(el, st, d) {
st.run = d.run;
st.stream = d.stream;
if (!st.navSet) { // someone else's public rollout belongs to Community, not to My rollouts
st.navSet = true;
const mine = !!d.run.is_owner;
document.querySelectorAll("[data-nav]").forEach((a) => a.classList.toggle("on", a.dataset.nav === (mine ? "runs" : "community")));
const c = el.querySelector(".crumbs a");
if (c && !mine) { c.href = "/community"; c.textContent = "Community"; }
}
const fresh = d.events.filter((e) => e.i >= st.after);
st.events.push(...fresh);
st.after = st.events.length ? st.events[st.events.length - 1].i + 1 : st.after;
renderHead(el, st);
renderPhases(el, st);
appendTimeline(el, st, fresh);
// history appears at once; only steps that arrive while you watch slide in
if (!st.primed) { el.querySelectorAll("#rp-tl .ev").forEach((li) => li.classList.add("still")); st.primed = true; }
renderGrade(el, st);
renderCost(el, st);
renderMeta(el, st);
renderTail(el, st);
$("#rp-follow-wrap", el).hidden = !isLive(st.run);
if (!st.run.is_owner) $("#rp-cost .fine", el)?.remove();
if (st.follow && fresh.length && isLive(st.run)) $("#rp-tail", el).scrollIntoView({ behavior: "smooth", block: "end" });
}
// Built once and then only its text changes: re-rendering every second restarted the spinner and made it flicker.
function renderTail(el, st) {
const t = $("#rp-tail", el);
if (!t) return;
if (!isLive(st.run)) { t.innerHTML = ""; return; }
let msg = $(".live-msg", t);
if (!msg) {
t.innerHTML = `
${spinner()}
`;
msg = $(".live-msg", t);
}
const text = liveHint(st);
if (msg.textContent !== text) msg.textContent = text;
}
function liveHint(st) {
const s = st.run.status;
if (s === "queued") return "Queued…";
if (s === "starting") return "Starting a sandbox from the task's image. Big images take a minute or two.";
if (s === "setup") return "Preparing the environment…";
if (s === "verifying") return "Grading…";
const last = st.events[st.events.length - 1];
if (last?.kind === "partial") return `Writing… ${last.chars.toLocaleString()} characters${last.thinking_chars ? `, ${last.thinking_chars.toLocaleString()} thinking` : ""}`;
const sm = st.stream;
if (sm) {
const n = sm.text + sm.thinking + sm.tool;
if (!n) return `Waiting for the model's reply · ${dur(sm.age)}`;
const parts = [sm.thinking && `${sm.thinking.toLocaleString()} thinking`, sm.text && `${sm.text.toLocaleString()} message`,
sm.tool && `${sm.tool.toLocaleString()} in a tool call`].filter(Boolean);
return `The model is writing · ${n.toLocaleString()} characters so far (${parts.join(", ")}) · ${dur(sm.age)}`;
}
const since = last ? Math.max(0, (Date.now() / 1000 - (st.run.started_at || st.run.created_at)) - last.t) : 0;
return since > 45 ? `The model is working · ${dur(since)} since the last step. Long files take a while to write.` : "The model is working…";
}
function renderHead(el, st) {
const r = st.run;
if (!r) return;
const start = r.started_at || r.created_at, end = r.finished_at || Date.now() / 1000;
const owner = !!r.is_owner;
const action = !owner ? `${icon("play", 13)}Run it yourself`
: isLive(r)
? (st.stopping ? `` : ``)
: `${icon("refresh", 13)}Run again`;
const vis = owner
? ``
: "";
$("#rp-head", el).innerHTML = `
`;
}).join("");
}
function appendTimeline(el, st, events) {
const tl = $("#rp-tl", el);
for (const e of events) {
if (e.kind === "phase" && e.status === "start") st.phase = e.name;
const html = item(e, st);
if (html) { tl.insertAdjacentHTML("beforeend", html); if (e.kind === "tool" || e.kind === "text") st.steps++; }
}
$("#rp-n", el).textContent = st.steps ? `${st.steps} step${st.steps === 1 ? "" : "s"}` : "";
const empty = $(".tl-empty", el);
if (!tl.children.length && !isLive(st.run)) { if (!empty) tl.insertAdjacentHTML("afterend", `
No agent steps were recorded.
`); }
else if (empty) empty.remove();
}
// A whole trace as timeline rows, for pages that show several at once (the comparison). `skip` leaves out kinds
// the page shows elsewhere, such as the prompt when every rollout got the same one.
export function traceHtml(id, events, { skip = [] } = {}) {
const st = { id, phase: null };
let steps = 0;
const html = events.map((e) => {
if (e.kind === "phase" && e.status === "start") st.phase = e.name;
if (skip.includes(e.kind)) return "";
const h = item(e, st);
if (h && (e.kind === "tool" || e.kind === "text")) steps++;
return h;
}).join("");
return { html, steps };
}
const row = (cls, ic, t, body) => `
${icon(ic, 14)}
${body}
${t}
`;
function item(e, st) {
const t = dur(e.t);
const summary = (inner) => `${icon("chevronRight", 13, "chev")}${inner}`;
switch (e.kind) {
case "prompt": return row("ev-prompt", "doc", t, `${summary(`Instructions to the model${(e.text || "").length.toLocaleString()} characters`)}
${esc(e.text)}
${e.harness === "nemo-gym" || st.run.runner === "nemo-gym" ? "The native Responses request: conversation history and function definitions. The reference action is withheld." : e.harness === "opencode"
? "The task's instructions, sent as the first user message. OpenCode adds its own system prompt and tool definitions."
: "The whole request: one user message, no system prompt."}
`);
case "text": return row("ev-text", "message", t, `
${r.endpoint ? `Tokens are billed by your endpoint (${esc(r.endpoint.host)}); the sandbox by Hugging Face.` : `Billed to your Hugging Face account at ${esc(r.provider || "the provider")}'s price.`}
`;
}
async function setVisibility(el, st, to, btn) {
try {
await api(`/api/runs/${encodeURIComponent(st.id)}/visibility`, { method: "POST", body: { visibility: to } });
st.run.visibility = to;
renderHead(el, st);
if (to === "public") {
const r = (btn || $("#rp-vis", el))?.getBoundingClientRect();
confetti(r ? r.left + r.width / 2 : innerWidth / 2, r ? r.top : innerHeight / 3);
toast("Public: thank you for sharing with the community");
} else toast("Private: only you can see this rollout");
} catch (e) { toast(e.message); }
}
function renderMeta(el, st) {
const r = st.run, p = r.params || {}, pv = r.provenance || {};
const row = (k, v) => (v == null || v === "" ? "" : `