// An environment Space, most often an OpenEnv server. Three sources, each named where it's used: // live what its running server says now (/api/spaces//live: OpenAPI, /metadata, /schema, MCP tools, Task API) // last seen that, kept from the last time it was seen running here (shown while it sleeps) // declared what its repository's files say at one revision (/api/spaces//files: openenv.yaml, models.py, // server/app.py, the environment's reward code, Dockerfile, pyproject), read without waking it // The page: an Interface table (how an agent acts, what it sees, the reward, tasks, web app, episodes), then the // playground (awake only), its app, tools, tasks, rewards, schemas, files, connecting, `openenv validate`'s criteria, // and the README. Status, wake and restart sit beside it. import { $, api, esc, md, ago, sk, emptyState, toast, fmt, spinner, storage, setMeta, ownerLink } from "./util.js"; import { icon } from "./icons.js"; import { AGENTS, agentHtml, agentName, chosenAgent, copyBtn, snippet } from "./connect.js"; import { getSession } from "./session.js"; import { schemaForm, readForm, wireForm } from "./schemaform.js"; import { value } from "./render.js"; import { fileViewer } from "./files.js"; import { supportPanel } from "./support.js"; import { taskContent } from "./space-task-view.js"; import { spaceMedia } from "./space-media.js"; const enc = (s) => s.split("/").map(encodeURIComponent).join("/"); const STAGE = { RUNNING: ["Running", "ok"], SLEEPING: ["Asleep", "faint"], PAUSED: ["Paused", "faint"], STOPPED: ["Stopped", "faint"], APP_STARTING: ["Starting", "live"], BUILDING: ["Building", "live"], RUNNING_BUILDING: ["Rebuilding", "live"], RUNNING_APP_STARTING: ["Restarting", "live"], RUNTIME_ERROR: ["Runtime error", "err"], BUILD_ERROR: ["Build error", "err"], CONFIG_ERROR: ["Config error", "err"], NO_APP_FILE: ["No app file", "err"], }; const stageOf = (x) => STAGE[x] || [String(x || "unknown").toLowerCase().replace(/_/g, " "), "faint"]; const STARTING = new Set(["APP_STARTING", "BUILDING", "RUNNING_BUILDING", "RUNNING_APP_STARTING"]); const KIND = { harbor: "OpenEnv × Harbor", openenv: "OpenEnv", "nemo-gym": "NeMo Gym server", gymnasium: "Gymnasium HTTP server", ors: "ORS server", mcp: "MCP server", api: "HTTP API" }; const shortSplit = (n) => (String(n).includes("/") ? String(n).split("/").filter(Boolean).pop() : String(n)); const IMAGE = /((^|_)(image|img|frame|screenshot|pixels|png|jpe?g|picture|photo|render)s?(_?(b64|data|url|bytes))?$|base64|(^|_)b64$)/i; const HIDDEN_OBS = new Set(["reward", "done", "metadata"]); let page = null; // this page's state; unmount ends its playground session, its polling and its file viewer export function unmount() { if (!page) return; clearTimeout(page.poll); page.observer?.disconnect(); page.viewer?.destroy(); document.removeEventListener("keydown", page.onKey); if (page.session) { fetch(`/api/spaces/${enc(page.spec)}/play`, { method: "POST", keepalive: true, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ op: "end", session: page.session }) }).catch(() => {}); } page = null; } // ── where a fact comes from ────────────────────────────────────────────────── const D = (me) => (me.F && !me.F.error ? me.F.declared || {} : {}); const seenOf = (me) => (me.L && !me.L.running ? me.L.last_seen || null : null); const probeOf = (me) => (me.L?.running ? me.L : seenOf(me)); // the live answer, or the last one kept const fileLink = (path, label) => `${esc(label || path.split("/").pop())}`; const SRC = { live: () => `live`, seen: (s) => `last seen ${esc(ago(s.checked_at))}`, file: (me, path) => `${path ? fileLink(path) : "its files"} @ ${esc(me.F.short)}`, session: () => `this session`, }; const probeSrc = (me) => (me.L?.running ? SRC.live() : SRC.seen(seenOf(me))); const codes = (xs, n = 8) => `${xs.slice(0, n).map((x) => `${esc(x)}`).join(" ")}${xs.length > n ? ` +${fmt.format(xs.length - n)}` : ""}`; const line = (html, src = "") => `
${html}${src}
`; const NONE = (text) => `${text}`; // the models.py class that is the Action (or Observation, State): the one server/app.py names, else the role's // class with the most fields function classOf(d, role) { const cls = d.models?.classes || []; const named = d.app?.[role]; return cls.find((c) => c.name === named && (c.fields || []).length) || cls.filter((c) => c.role === role && !c.kind) .sort((a, b) => (b.fields || []).length - (a.fields || []).length)[0] || null; } const propsOf = (schema) => Object.entries(schema?.properties || {}); const isImage = (k, p = {}) => IMAGE.test(k) || /image/i.test(p.format || "") || p.contentEncoding === "base64" || /image/i.test(p.contentMediaType || ""); const fieldList = (rows) => rows.map(([k, img]) => `${esc(k)}${img ? ` image` : ""}`).join(" "); const declaredTools = (d) => (d.environment?.tools?.length ? [d.environment.tools.map((t) => t.name), d.environment.path] : d.manifest?.validation?.capabilities?.declared_tools?.length ? [d.manifest.validation.capabilities.declared_tools, d.manifest.path] : d.manifest?.tools?.length ? [d.manifest.tools.map((t) => t.name), d.manifest.path] : [null, null]); const declaredTasks = (d) => d.manifest?.tasks || null; const taskCounts = (d) => d.manifest?.validation?.capabilities?.declared_task_count || {}; const reward = (d) => d.manifest?.validation?.reward || null; const caps = (d) => d.manifest?.validation?.capabilities || {}; // ── the interface table ────────────────────────────────────────────────────── function interfaceRows(me) { const P = probeOf(me), d = D(me), F = me.F && !me.F.error ? me.F : null; const rows = []; // how the agent acts const acts = []; if (P?.mcp?.length) acts.push(line(`MCP tools: ${codes(P.mcp.map((t) => t.name))}`, probeSrc(me))); else if (P?.schema?.action?.properties) acts.push(line(`Action fields: ${fieldList(propsOf(P.schema.action).filter(([k]) => k !== "metadata").map(([k]) => [k]))}`, probeSrc(me))); else if (me.L?.running && me.L.api?.length) acts.push(line(`HTTP routes: ${codes(me.L.api.map((r) => `${r.method} ${r.path}`), 6)}`, SRC.live())); if (!acts.length && F) { const [tools, where] = declaredTools(d); const A = classOf(d, "action"); if (tools?.length) acts.push(line(`MCP tools: ${codes(tools)}`, SRC.file(me, where))); else if (d.app?.mcp) acts.push(line(`MCP tools (${esc(d.app.action)})`, SRC.file(me, d.app.path))); else if (A) acts.push(line(`${esc(A.name)}${(A.fields || []).length ? `: ${fieldList(A.fields.map((f) => [f.name]))}` : ""}`, SRC.file(me, d.models.path))); else if (d.manifest?.legacy?.action) acts.push(line(`${esc(d.manifest.legacy.action)}`, SRC.file(me, d.manifest.path))); } rows.push(["Agent acts by", acts]); // what it sees const sees = []; if (P?.schema?.observation?.properties) { const obs = propsOf(P.schema.observation).filter(([k]) => !HIDDEN_OBS.has(k)); if (obs.length) sees.push(line(`${fieldList(obs.map(([k, p]) => [k, isImage(k, p)]))}`, probeSrc(me))); } if (!sees.length && F) { const O = classOf(d, "observation"); if (O && (O.fields || []).length) sees.push(line(`${esc(O.name)}: ${fieldList(O.fields.filter((f) => !HIDDEN_OBS.has(f.name)).map((f) => [f.name, f.image]))}`, SRC.file(me, d.models.path))); else if (d.app?.mcp) sees.push(line("tool results (MCP)", SRC.file(me, d.app.path))); else if (d.manifest?.legacy?.observation) sees.push(line(`${esc(d.manifest.legacy.observation)}`, SRC.file(me, d.manifest.path))); } rows.push(["Agent sees", sees]); // the reward const rw = []; if (me.rewards.length) { const lo = Math.min(...me.rewards), hi = Math.max(...me.rewards); rw.push(line(`observed ${lo === hi ? num(lo) : `${num(lo)} to ${num(hi)}`} over ${fmt.format(me.rewards.length)} step${me.rewards.length === 1 ? "" : "s"}`, SRC.session())); } if (P?.rewards) rw.push(line("every step returns one", probeSrc(me))); if (P?.graders?.length) rw.push(line(`graded on ${codes(P.graders, 4)}`, probeSrc(me))); if (F) { const r = reward(d), c = caps(d), mp = d.manifest?.path; if (r?.range) rw.push(line(`range ${esc(`[${r.range.map(num).join(", ")}]`)}`, SRC.file(me, mp))); if (c.verifier?.kind) rw.push(line(`verifier: ${esc(c.verifier.kind)}${c.verifier.entry ? ` ${fileLinkIf(me, c.verifier.entry, d)}` : ""}`, SRC.file(me, mp))); if (c.llm_judged) rw.push(line(`LLM-judged${d.manifest.validation.judge?.model ? `: ${esc(d.manifest.validation.judge.model)}` : ""}`, SRC.file(me, mp))); const blocks = Object.keys(d.manifest?.blocks || {}).filter((k) => /reward|rubric|grader|scoring/.test(k)); if (blocks.length) rw.push(line(`${codes(blocks, 4)} in openenv.yaml`, SRC.file(me, mp))); const e = d.environment; if (e && !e.error) { if (e.llm_judge) rw.push(line(`an LLM judges${e.llm_modules?.length ? ` (${codes(e.llm_modules, 3)})` : ""}`, SRC.file(me, e.path))); if (e.rubric) rw.push(line("a Rubric scores it", SRC.file(me, e.path))); if (!e.llm_judge && !e.rubric && (e.functions?.length || e.reward_files?.length)) rw.push(line(`in code: ${e.functions?.length ? codes(e.functions, 3) : (e.reward_files || []).map((p) => fileLink(p)).join(", ")}`, SRC.file(me, e.path))); } } rows.push(["Reward", rw]); // tasks const tk = []; const splits = P?.task_api?.splits || []; if (splits.length) tk.push(line(splits.map((x) => `${esc(shortSplit(x.name))}${x.num_tasks != null ? ` ${fmt.format(x.num_tasks)}` : ""}`).join(" · "), probeSrc(me))); else if (P?.task_api) tk.push(line("a Task API, with no splits listed", probeSrc(me))); if (F && !tk.length) { const counts = Object.entries(taskCounts(d)), list = declaredTasks(d); if (counts.length) tk.push(line(counts.map(([k, n]) => `${esc(k)} ${fmt.format(n)}`).join(" · "), SRC.file(me, d.manifest.path))); else if (list?.length) tk.push(line(`${fmt.format(list.length)} declared: ${codes(list.map((t) => t.id || t.name || "?"), 6)}`, SRC.file(me, d.manifest.path))); } rows.push(["Tasks", tk]); // its web app const web = []; if (me.L?.running) web.push(line(me.L.ui ? `${esc(me.L.ui)}` : NONE("none"), SRC.live())); else if (seenOf(me)) web.push(line(seenOf(me).ui ? `${esc(seenOf(me).ui)}` : NONE("none"), SRC.seen(seenOf(me)))); if (F && !me.L?.running) { const df = d.dockerfile, card = d.card || {}; if (d.app?.gradio_builder) web.push(line(`a Gradio tab of its own${d.app.custom_tab_name ? `, ${esc(d.app.custom_tab_name)}` : ""}`, SRC.file(me, d.app.path))); else if ((d.app?.routes || []).some((r) => /^GET \/web\/?$/.test(r))) web.push(line("a page of its own at /web", SRC.file(me, d.app.path))); if (df && df.web_interface != null) web.push(line(df.web_interface ? `OpenEnv's web interface at ${esc(card.base_path || "/web")}` : "web interface off", SRC.file(me, df.path))); } rows.push(["Web app", web]); // episodes const ep = []; const mode = P?.mode; if (P && (P.openenv || P.framework === "openenv" || P.framework === "harbor")) { ep.push(line(mode === "production" ? "MCP only: production mode, no reset, step or state" : "reset, step, state; one WebSocket per session", probeSrc(me))); } else if (me.L?.running && me.L.framework) ep.push(line(esc(KIND[me.L.framework] || me.L.framework), SRC.live())); if (F && d.app && !d.app.error) { const bits = [d.app.max_concurrent_envs != null ? `up to ${esc(String(d.app.max_concurrent_envs))} at once` : null, d.app.mode ? `mode ${esc(String(d.app.mode))}` : null, !P && d.app.factory ? `${esc(d.app.factory)}` : null].filter(Boolean); if (bits.length) ep.push(line(bits.join(", "), SRC.file(me, d.app.path))); } rows.push(["Episodes", ep]); return rows; } const num = (x) => String(+Number(x).toFixed(4)); const fileLinkIf = (me, path, d) => (me.F.files.some((f) => f.path === path) ? fileLink(path) : (d.manifest?.path && me.F.files.some((f) => f.path === `${me.F.root}/${path}`) ? fileLink(`${me.F.root}/${path}`, path) : `${esc(path)}`)); function interfaceTable(me) { if (!me.L && !me.F) return ifaceSkeleton(); const rows = interfaceRows(me); const pending = !me.F || !me.L; return `${supportPanel(me.L?.support)}
${rows.map(([k, vals]) => `
${esc(k)}
${vals.length ? vals.join("") : pending ? sk.line(40) : NONE(emptyFor(me, k))}
`).join("")}
${me.F?.error ? `

${icon("alert", 12)} Couldn't read its files: ${esc(me.F.error)}

` : ""}`; } const emptyFor = (me, k) => (k === "Tasks" ? "none declared" : k === "Web app" ? "none declared" : !me.L?.running && !seenOf(me) ? "not declared; wake it to ask its server" : "none"); const ifaceSkeleton = () => `
${["Agent acts by", "Agent sees", "Reward", "Tasks", "Web app", "Episodes"].map((k, i) => `
${k}
${sk.line([62, 74, 48, 36, 30, 52][i])}
`).join("")}
`; // ── sections ───────────────────────────────────────────────────────────────── // an OpenEnv × Harbor server: what it can run, and the two ways to run its tasks function harborSection(L) { const c = L.harbor_caps; if (!c) return `

It runs Harbor tasks as rollouts (run_rollout), but didn't say what it can run.

`; const ok = c.sandboxes.filter((x) => x.available), validated = c.harnesses.filter((h) => h.status === "validated"); const hub = c.datasets.filter((d) => d.hub); return `
Model engine
${c.engine.url ? `its own${c.engine.model ? `: ${esc(c.engine.model)}` : ""}` : `none of its own: each run_rollout call names one (llm_url, model, api_key)`}
Sandboxes
${ok.length ? ok.map((x) => `${esc(x.name)}`).join(" ") : `none available`}
Agents
${validated.map((h) => `${esc(h.name)}`).join(" ")}${c.harnesses.length > validated.length ? ` and ${c.harnesses.length - validated.length} more, not validated` : ""}
Serves
${c.datasets.map((d) => `${esc(d.hub || d.name)}${d.num_tasks != null ? ` ${fmt.format(d.num_tasks)} tasks` : ""}`).join("
")}
${icon("user", 14)}On your account

${hub.length ? "Open the dataset in the explorer: every task runs on an HF Sandbox billed to you, with the agent and model you pick." : "Its datasets are local to the Space, so they can't be opened in the explorer."}

${hub.map((d) => `${icon("database", 13)}${esc(d.hub)}`).join("")}
${icon("box", 14)}On this Space

${L.ui ? "Its own app runs rollouts with its sandboxes; sign in there." : ""} ${c.engine.url ? "Or call run_rollout from the playground, on its operator's engine." : "Calling run_rollout needs a model engine, which this explorer never fills in with your token."}

${L.ui ? `` : ""}${c.engine.url ? `` : ""}
`; } function appSection(me) { const { s, L } = me; if (!L) return sk.box(160); if (L.running && L.ui) { const src = L.host + L.ui; if (!me.appOpen) { return `
${esc(src.replace("https://", ""))} ${icon("external", 13)}New tab
`; } return `
${esc(src.replace("https://", ""))} ${icon("external", 15)}
${spinner()}Loading its app…
`; } if (L.running) return `

This server has no web page of its own${L.base_path ? ` at ${esc(L.base_path)}` : ""}, /web or /. The playground calls its API.

`; const thumb = `The Hub's picture of ${esc(s.id)}`; return `${thumb}

${STARTING.has(L.stage) ? "Starting: its app opens here when it's up." : "Its app opens here when the Space is running."}

`; } // what the playground can do here: call its MCP tools, take actions (reset/step), or call its own HTTP routes const modesOf = (L) => [...(L.mcp?.length ? ["tools"] : []), ...(L.step_api && !L.mcp?.length ? ["action"] : []), ...(L.api?.length ? ["routes"] : [])]; const MODE = { tools: "Its tools", action: "Actions", routes: "Its routes" }; function playSection(me) { const L = me.L; if (!L) return sk.box(160); if (L.running) return playground(L); if (STARTING.has(L.stage)) return `

${spinner("xs")}Starting. The playground opens when it's running.

`; if (L.stage === "SLEEPING") { return `

Asleep. Wake it to play an episode on it; it takes a minute or two.

`; } return `

It isn't running (${esc(stageOf(L.stage)[0].toLowerCase())}), so there is nothing to play. Its status, beside, says what can be done.

`; } function playground(L) { const modes = modesOf(L); if (!modes.length) return `

This Space has no API to play with: no /reset and /step, no MCP tools, no routes in an /openapi.json.

`; const tools = L.mcp || []; const splits = L.task_api?.splits || []; const resetOpts = `
${icon("chevronRight", 14, "chev")}Reset options
${splits.length ? `
` : ""}
`; const pane = { tools: () => `

`, action: () => `
Action
${schemaForm(L.schema?.action || {}, { prefix: "pga" })}
`, routes: () => `

`, }; return `
${L.openenv && L.step_api ? `${resetOpts}` : ""} ${modes.length > 1 ? `${modes.map((m, i) => ``).join("")}` : ""} ${modes.map((m, i) => `
${pane[m]()}
`).join("")}

Requests go to the Space itself. ${L.openenv ? "An OpenEnv WebSocket keeps your episode together." : L.api?.length ? "HTTP cookies are isolated per explorer session; the server controls episode state." : "Tool state depends on the server's MCP implementation."} Sessions end after 10 idle minutes. The Space's owner pays for its hardware.

`; } function tasksSection(me) { const P = probeOf(me), d = D(me); if (me.L?.running && me.L.task_api) return taskBrowser(me); const out = []; if (P?.task_api && !P.task_api.splits?.length) out.push(`
Its Task API lists no splits${probeSrc(me)}
`); const splits = P?.task_api?.splits || []; if (splits.length) { out.push(`
Its Task API${probeSrc(me)}
${splits.map((x) => ``).join("")}
SplitTasks
${esc(x.name)}${x.num_tasks != null ? fmt.format(x.num_tasks) : "–"}${x.default ? "default" : ""}

Wake it to browse them.

`); } if (!me.F) return out.join("") || sk.lines(80, 60, 70); const counts = Object.entries(taskCounts(d)), list = declaredTasks(d); if (counts.length) { out.push(`
Declared counts${SRC.file(me, d.manifest.path)}
${counts.map(([k, n]) => ``).join("")}
SplitTasks
${esc(k)}${fmt.format(n)}
`); } if (list?.length) { const cols = ["id", "name", "difficulty", "max_steps", "description"].filter((k) => list.some((t) => t[k] != null && t[k] !== "")); out.push(`
Declared tasks${SRC.file(me, d.manifest.path)}
${cols.map((k) => ``).join("")}${list.slice(0, 200).map((t) => `${cols.map((k) => ``).join("")}`).join("")}
${esc(k.replace("_", " "))}
${t[k] == null ? "" : k === "id" ? `${esc(String(t[k]))}` : k === "description" ? inl(typeof t[k] === "object" ? JSON.stringify(t[k]) : String(t[k])) : esc(typeof t[k] === "object" ? JSON.stringify(t[k]) : String(t[k]))}
${d.manifest.withheld?.length ? `

${icon("shield", 12)} Left out, as they may hold the answer: ${d.manifest.withheld.map((k) => `${esc(k)}`).join(", ")}.

` : ""}`); } return out.join("") || `

${me.L?.running ? "No Task API, and no tasks declared in its files." : "No tasks declared in its files."}${!me.L?.running && !seenOf(me) ? " Its server may serve some: wake it to ask." : ""}

`; } function taskCatalog(me) { const api = me.L.task_api, environments = api.environments || [api]; const selected = environments.find((e) => e.env === me.taskPage.env) || environments[0]; me.taskPage.env = selected.env; return selected; } function taskBrowser(me) { const { splits } = taskCatalog(me), envs = me.L.task_api.environments || [me.L.task_api]; if (!splits.some((s) => s.name === me.taskPage.split)) me.taskPage.split = (splits.find((s) => s.default) || splits[0])?.name || ""; return `
${envs.length > 1 ? `` : ""}

Choose a split, then a task to inspect its inputs. Counts come from this Space's Task API.

${splits.map((x) => ``).join("")}
${sk.lines(90, 90, 90, 90)}
`; } function toolsSection(me) { const P = probeOf(me), d = D(me); if (P?.mcp?.length) { const own = new Set(P.mcp.map((t) => t.name)); const extra = (P.bridge_tools || []).filter((t) => !own.has(t.name)); return `${me.L?.running ? "" : `
${fmt.format(P.mcp.length)} tools${probeSrc(me)}
`}${toolsRef(P.mcp, !!me.L?.running)} ${extra.length ? `
Also available through the explorer's bridge
${toolsRef(extra, false)}` : ""}`; } if (!me.F && !P) return sk.lines(70, 50, 60); const e = d.environment; if (e?.tools?.length) { return `
Registered in its code${SRC.file(me, e.path)}
${fieldTable(e.tools.map((t) => ({ name: t.name, typeHtml: t.params.length ? `${t.params.map((p) => `${esc(p)}`).join("")}` : NONE("none"), desc: t.description || "" })), { head: ["Tool", "Parameters", "What it does"] })} ${me.L?.running ? "" : `

The ones a running server offers can depend on how it's configured.

`}`; } const [tools, where] = declaredTools(d); if (tools?.length) return `
Declared${SRC.file(me, where)}

${codes(tools, 40)}

`; if (P?.bridge_tools?.length) { // a reset/step server: its tools are its episode calls, with its own action's fields const [org, name] = me.spec.split("/"); return `
${fmt.format(P.bridge_tools.length)} tools, through ${P.openenv ? "reset and step" : "its HTTP API"}${SRC.live()}

${P.openenv ? "The bridge exposes reset, step and state with the environment's action schema." : "The bridge exposes the server's published routes. Path parameters, query parameters and body have separate inputs."} These are the available tools (/mcp/${esc(org)}/${esc(name)}, under Connect); the playground calls the same ones.

${toolsRef(P.bridge_tools, false)}`; } if (me.L?.running || P) return `

No MCP tools of its own${(P?.openenv || P?.step_api) ? ": an agent acts through reset and step (over MCP, the bridge offers those as tools)" : ""}.

`; const A = classOf(d, "action"); if (A) return `

No MCP tools declared: an agent acts through reset and step, with ${esc(A.name)}. ${SRC.file(me, d.models.path)}

`; return `

None declared in its files.${me.F ? " Wake it to ask its server." : ""}

`; } function toolsRef(tools, tryable) { return `
${tools.map((t) => { const props = Object.entries(t.inputSchema?.properties || {}); return `
${icon("chevronRight", 13, "chev")}${esc(t.name)} ${props.length ? `(${props.map(([k]) => esc(k)).join(", ")})` : ""}${esc((t.description || t.title || "").split("\n")[0])} ${tryable ? `` : ""}
${t.description && t.description.includes("\n") ? docHtml(t.description, 1500) : ""} ${props.length ? fieldTable(schemaRows(t.inputSchema, t.inputSchema.$defs || {}, { images: false }), { head: ["Parameter", "Type", "What it is"] }) : `

${t.inputSchema ? "No parameters." : "The server doesn't list its parameters."}

`}
`; }).join("")}
`; } function rewardsSection(me) { const P = probeOf(me), d = D(me); if (!me.F && !me.L) return sk.lines(70, 50, 60); const rows = []; if (me.rewards.length) rows.push(["This session", `${me.rewards.slice(-12).map(num).join(", ")}${me.rewards.length > 12 ? " …" : ""} ${SRC.session()}`]); if (P?.rewards) rows.push(["Each step", `its observation carries reward ${probeSrc(me)}`]); if (P?.graders?.length) rows.push(["Graded on", `${codes(P.graders)} ${probeSrc(me)}`]); if (me.F && !me.F.error) { const v = d.manifest?.validation, mp = d.manifest?.path; if (v?.reward) { const r = v.reward; if (r.range) rows.push(["Range", `[${r.range.map(num).join(", ")}] ${SRC.file(me, mp)}`]); const tol = ["oracle_tolerance", "floor_margin", "variance_tolerance"].filter((k) => r[k] != null); if (tol.length) rows.push(["Tolerances", `${tol.map((k) => `${esc(k.replace("_", " "))} ${num(r[k])}`).join(", ")} ${SRC.file(me, mp)}`]); } const c = v?.capabilities; if (c?.verifier?.kind) rows.push(["Verifier", `${esc(c.verifier.kind)}${c.verifier.entry ? ` ${fileLinkIf(me, c.verifier.entry, d)}` : ""} ${SRC.file(me, mp)}`]); if (c && "llm_judged" in c) rows.push(["LLM-judged", `${c.llm_judged ? "yes" : "no"}${v.judge?.model ? `: ${esc(v.judge.model)}${v.judge.version ? ` ${esc(v.judge.version)}` : ""}` : ""} ${SRC.file(me, mp)}`]); if (c?.oracle) rows.push(["Oracle", `${esc(c.oracle.form || "")} ${c.oracle.location ? fileLinkIf(me, c.oracle.location, d) : ""} ${SRC.file(me, mp)}`]); const e = d.environment; if (e && !e.error) { const files = [e.path, ...(e.reward_files || [])]; rows.push(["Its code", `${files.map((p) => fileLink(p, p)).join(", ")}${e.functions?.length ? `
${codes(e.functions, 12)}
` : ""}`]); if (e.llm_judge) rows.push(["An LLM judges", `${e.llm_modules?.length ? codes(e.llm_modules) : "yes"} ${SRC.file(me, e.path)}`]); if (e.rubric) rows.push(["Rubric", `an OpenEnv Rubric ${SRC.file(me, e.path)}`]); } } const blocks = Object.entries(D(me).manifest?.blocks || {}).filter(([k]) => /reward|rubric|grader|scoring/.test(k)); const yaml = blocks.length ? `
In openenv.yaml${SRC.file(me, d.manifest.path)}
${blocks.map(([k, x]) => `
${esc(k)}${isObj(x) ? fieldsView(x, 1) : `
${value(x, k)}
`}
`).join("")}` : ""; if (!rows.length && !yaml) return `

${me.F ? "Nothing about its reward is declared in its files." : ""}${!me.L?.running ? " Wake it and play an episode to see what it gives." : " Play an episode to see what it gives."}

`; return `${rows.length ? `
${rows.map(([k, v]) => `
${esc(k)}
${v}
`).join("")}
` : ""}${yaml}`; } // ── field tables: /schema, models.py classes and tool parameters, drawn the same way ── // A field's name and type never break inside a word: unions break at `|`, enum values wrap as a list, and at phone // width each field stacks into one block. const inl = (s) => esc(s).replace(/`([^`\n]+)`/g, "$1"); const DOC_SECTION = /(^|\n)[ \t]*(Attributes|Args|Arguments|Parameters|Fields|Returns|Raises|Examples?|Notes?)[ \t]*:[ \t]*(\n|$)/; // a docstring's own prose (its Attributes/Args lists repeat what the table says), rendered as Markdown function docHtml(text, max = 700) { if (!text) return ""; let t = String(text); const cut = t.search(DOC_SECTION); if (cut >= 0) t = t.slice(0, cut); t = t.replace(/^[ \t]+/gm, "").replace(/\[`~?([^`\]]+)`\]/g, (_, x) => `\`${x.split(".").pop()}\``).trim(); if (!t) return ""; if (t.length > max) t = t.slice(0, max).replace(/\s+\S*$/, "") + " …"; return `
${md(t)}
`; } function typeHtml(t) { const parts = String(t || "any").split(/\s*\|\s*/).filter(Boolean); return `${parts.map((x) => `${esc(x)}`).join(`|`)}`; } const enumHtml = (vals) => (vals?.length ? `${vals.slice(0, 40).map((x) => `${esc(typeof x === "string" ? x : JSON.stringify(x))}`).join("")}${vals.length > 40 ? `+${vals.length - 40}` : ""}` : ""); // a JSON-schema property as {type, enumVals, nested}: $refs into $defs followed, unions joined, arrays named by item function propType(p, defs, depth = 0) { if (!p || typeof p !== "object" || depth > 4) return { type: "any" }; if (p.$ref) { const name = String(p.$ref).split("/").pop(), d = defs?.[name]; if (d?.enum) return { type: name, enumVals: d.enum }; if (d?.properties) return { type: name, nested: d }; return { type: name }; } if (p.enum) return { type: p.type || "enum", enumVals: p.enum }; if (p.const !== undefined) return { type: JSON.stringify(p.const) }; const alts = p.anyOf || p.oneOf; if (alts) { const parts = alts.map((x) => propType(x, defs, depth + 1)); return { type: parts.map((x) => x.type).join(" | "), enumVals: parts.find((x) => x.enumVals)?.enumVals, nested: parts.find((x) => x.nested)?.nested }; } if (p.type === "array") { const it = propType(p.items, defs, depth + 1); return { type: `list[${it.type}]`, enumVals: it.enumVals, nested: it.nested }; } if (p.type === "object" && p.properties) return { type: p.title || "object", nested: p }; if (p.type === "object" && p.additionalProperties && typeof p.additionalProperties === "object") return { type: `dict[str, ${propType(p.additionalProperties, defs, depth + 1).type}]` }; return { type: Array.isArray(p.type) ? p.type.join(" | ") : p.type || "any" }; } function schemaRows(schema, defs, { images = true, skip = [] } = {}) { const req = new Set(schema?.required || []); return Object.entries(schema?.properties || {}).filter(([k]) => !skip.includes(k)).map(([k, p]) => { const t = propType(p, defs); return { name: k, required: req.has(k), image: images && isImage(k, p), type: t.type, enumVals: t.enumVals, desc: p.description || "", def: p.default !== undefined && p.default !== null ? JSON.stringify(p.default) : null, nested: t.nested ? schemaRows(t.nested, defs, { images }) : null }; }); } function fieldTable(rows, { head = ["Field", "Type", "What it is"], depth = 0 } = {}) { if (!rows.length) return `

No fields.

`; return `
${head.map((h) => ``).join("")}${rows.map((r) => ` ${r.nested?.length && depth < 2 ? `` : ""}`).join("")}
${esc(h)}
${esc(r.name)}${r.required ? `required` : ""}${r.image ? `image` : ""} ${r.typeHtml || typeHtml(r.type)}${enumHtml(r.enumVals)}${r.limits?.length ? `${esc(r.limits.join(", "))}` : ""} ${r.desc ? inl(r.desc) : ""}${r.def != null ? `default ${esc(String(r.def).slice(0, 80))}` : ""}
${icon("chevronRight", 13, "chev")}${esc(r.name)}: ${fmt.format(r.nested.length)} field${r.nested.length === 1 ? "" : "s"}${fieldTable(r.nested, { depth: depth + 1 })}
`; } function schemaTable(title, schema) { if (!schema || !schema.properties) return ""; const defs = schema.$defs || schema.definitions || {}; return `
${esc(title)}${schema.title ? ` ${esc(schema.title)}` : ""}
${docHtml(schema.description)}${fieldTable(schemaRows(schema, defs, { images: title !== "Action" }))}`; } // models.py's `Literal["a", "b"]` as enum values function literalVals(t) { const m = String(t || "").match(/Literal\[(.*?)\]/); if (!m) return null; return (m[1].match(/"[^"]*"|'[^']*'|[^,\s][^,]*/g) || []).map((x) => x.trim().replace(/^["']|["']$/g, "")); } function classTable(title, c) { const rows = (c.fields || []).map((f) => { const lit = literalVals(f.type); return { name: f.name, required: f.required, image: f.image, type: lit ? f.type.replace(/Literal\[.*?\]/, "Literal") : f.type, enumVals: lit, limits: f.limits, desc: f.description || "", def: f.default ?? null }; }); return `
${esc(title)} ${esc(c.name)}${c.bases?.length ? ` (${esc(c.bases.join(", "))})` : ""}
${docHtml(c.doc)}${rows.length ? fieldTable(rows) : `

No fields of its own.

`}`; } function schemasSection(me) { const P = probeOf(me), d = D(me); const schema = P?.schema; if (schema && (schema.action || schema.observation)) { return `
From its /schema${probeSrc(me)}
${schemaTable("Action", schema.action) + schemaTable("Observation", schema.observation) + schemaTable("State", schema.state)}`; } if (P?.api?.length) return `
From its OpenAPI${probeSrc(me)}
` + P.api.map((r) => `
${esc(r.method)} ${esc(r.path)}${r.summary ? `

${esc(r.summary)}

` : ""} ${r.query_schema?.properties && Object.keys(r.query_schema.properties).length ? schemaTable("Query parameters", r.query_schema) : ""} ${r.schema ? (schemaTable("Request body", r.schema) || snippet(JSON.stringify(r.schema, null, 2))) : '

No request body schema published.

'}
`).join(""); if (!me.F) return sk.lines(70, 50, 60); const m = d.models; if (!m) return `

No models.py in its files.${!me.L?.running ? " Wake it to ask its server for /schema." : ""}

`; if (m.error) return `

${fileLink(m.path, m.path)}: ${esc(m.error)}.

`; const main = ["action", "observation", "state"].map((r) => [r, classOf(d, r)]).filter(([, c]) => c); const shown = new Set(main.map(([, c]) => c.name)); const rest = (m.classes || []).filter((c) => !shown.has(c.name)); return `
Declared in its code${SRC.file(me, m.path)}
${main.map(([r, c]) => classTable(r[0].toUpperCase() + r.slice(1), c)).join("") || `

No Action, Observation or State classes in it.

`} ${rest.length ? `
${icon("chevronRight", 14, "chev")}${fmt.format(rest.length)} more class${rest.length === 1 ? "" : "es"} in it
${rest.map((c) => c.kind === "enum" ? `
Enum ${esc(c.name)}

${codes(c.values || [], 30)}

` : classTable(c.role ? c.role[0].toUpperCase() + c.role.slice(1) : "Class", c)).join("")}
` : ""}`; } const KEY_LABEL = { manifest: "openenv.yaml", models: "models.py", app: "server/app.py", environment: "environment", client: "client.py", pyproject: "pyproject.toml", dockerfile: "Dockerfile", discovery: "discovery.json", inference: "inference.py", readme: "README", lock: "uv.lock" }; function filesSection(me) { if (!me.F) return `
${sk.line(60, 14)}
${sk.box(320)}`; if (me.F.error) return `

Couldn't list its files: ${esc(me.F.error)}

`; const F = me.F; const keys = Object.entries(F.keys || {}).filter(([k]) => KEY_LABEL[k]); return `${keys.length ? `` : ""}

${fmt.format(F.total)} file${F.total === 1 ? "" : "s"} at ${esc(F.short)}${F.dropped ? `; ${fmt.format(F.dropped)} more left out: vendored OpenEnv, build output, caches` : ""}${F.truncated ? "; the listing stops here" : ""}.

`; } // ── connect ────────────────────────────────────────────────────────────────── const CONNECT = [["agents", "Coding agents"], ["python", "Python"], ["docker", "Docker"], ["fork", "Fork"]]; function connectSection(me) { const tab = CONNECT.some(([k]) => k === me.connectTab) ? me.connectTab : "agents"; return `
${CONNECT.map(([k, label]) => ``).join("")}
${connectPane(me, tab)}
`; } function connectPane(me, tab) { const spec = me.spec, L = me.L, P = probeOf(me), d = D(me); const host = L?.host || (me.F?.subdomain ? `https://${me.F.subdomain}.hf.space` : ""); if (tab === "agents") { const url = `${location.origin}/mcp/${spec}`; const own = (P?.mcp || []).map((t) => t.name); const routeTools = L?.running ? (L.api || []).map((r) => r.path.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase()).map((b, i) => (L.api[i].method === "POST" || b.startsWith("get_") ? b : `get_${b}`)).filter((n) => !own.includes(n)) : []; const openenv = P?.openenv || P?.step_api; const tools = P?.bridge_tools ? P.bridge_tools.map((t) => t.name) : P ? [...own, ...(openenv && P.step_api && !own.includes("reset") ? ["reset", ...(own.length ? [] : ["step"]), "state"] : []), ...routeTools, ...(P.task_api ? ["list_splits", "list_tasks", "get_task"] : [])] : []; const chosen = chosenAgent(); return `
MCP${esc(url)}${copyBtn(url)}

Connect through the explorer's MCP bridge. ${openenv ? "An OpenEnv WebSocket keeps each agent's episode together." : "Each agent connection has its own HTTP cookies and MCP session; the server controls episode state."}${tools.length ? ` Available tools: ${tools.slice(0, 16).map((t) => `${esc(t)}`).join(" ")}${tools.length > 16 ? " …" : ""}.` : ""}${L && !L.running ? " A sleeping Space is woken on the first call." : ""} Images in observations come back as images.

${AGENTS.filter(([id]) => id !== "python").map(([id, label]) => ``).join("")}
${agentHtml(chosen === "python" ? "claude" : chosen, agentName(spec), url)}
`; } if (tab === "python") { if (P && !P.openenv) return `

Use the same MCP bridge from Python:

${snippet(agentHtmlText(spec))} ${P.api?.length ? `

Or inspect the HTTP schema directly. Reuse the client for subsequent calls so the server's cookies persist:

${snippet(`# pip install httpx\nimport httpx\n\nwith httpx.Client(base_url=${JSON.stringify(host)}, timeout=120) as client:\n schema = client.get("/openapi.json").json()\n print(schema["paths"])\n # Choose a route and supply its required path, query and body fields here.`)}` : ""}`; const action = actionExample(me); const generic = `# pip install openenv-core (no package of its own: plain dictionaries) from openenv.core.generic_client import GenericEnvClient with GenericEnvClient(base_url="${host || `https://.hf.space`}").sync() as env: result = env.reset() print(result.observation) result = env.step(${action}) print(result.reward, result.done)`; const root = me.F && !me.F.error ? me.F.root : ""; const pyproject = d.pyproject && !d.pyproject.error ? d.pyproject.path : null; const sub = pyproject && pyproject.includes("/") ? `#subdirectory=${pyproject.split("/").slice(0, -1).join("/")}` : ""; const auto = pyproject ? `# its own client and models, installed from the Space # pip install "git+https://huggingface.co/spaces/${spec}${sub}" from openenv import AutoEnv env = AutoEnv.from_env("${spec}")` : null; return `

Any OpenEnv server, from its URL${me.L && !me.L.running ? " (wake it first)" : ""}:

${snippet(generic)} ${auto ? `

Or with its own typed client${root ? ` (from ${esc(root)}/)` : ""}:

${snippet(auto)}` : ""}

Or over MCP, through this explorer's bridge:

${snippet(agentHtmlText(spec))}`; } if (tab === "docker") { const port = d.card?.app_port || d.dockerfile?.port || 7860; const sub = me.F?.subdomain || (host ? host.replace("https://", "").replace(".hf.space", "") : ""); if (!sub) return me.F ? `

The Hub gave no image name for this Space.

` : sk.lines(70, 50); const path = L?.ui || d.card?.base_path || "/"; return `

The Space's own image, from the Hub's registry, on your machine:

${snippet(`docker run -it -p 8000:${port} --platform=linux/amd64 registry.hf.space/${sub}:latest`)}

Then http://localhost:8000${esc(path)}, or point a client at http://localhost:8000. Secrets it needs (its own API keys) go in with -e NAME=value.

`; } if (!L?.openenv_verified) return `

OpenEnv compatibility has not been verified. To copy this Space, use its Hub repository:

${icon("external", 14)}Duplicate on Hugging Face`; return `

A copy of this Space on your account, to change and run as your own (needs hf auth login):

${snippet(`pip install openenv-core\nopenenv fork ${spec}`)}

Options: --repo-id you/name, --private, --hardware cpu-upgrade, --set-env KEY=VALUE, --set-secret KEY=VALUE.

`; } const agentHtmlText = (spec) => AGENTS.find(([id]) => id === "python")[2](agentName(spec), `${location.origin}/mcp/${spec}`).text; function actionExample(me) { const P = probeOf(me), d = D(me); if (P?.mcp?.length) return `{"type": "call_tool", "tool_name": "${P.mcp[0].name}", "arguments": {}}`; const props = propsOf(P?.schema?.action).filter(([k]) => k !== "metadata").slice(0, 3); if (props.length) return JSON.stringify(Object.fromEntries(props.map(([k, p]) => [k, p.default ?? (p.enum ? p.enum[0] : p.type === "string" ? "" : p.type === "integer" || p.type === "number" ? 0 : p.type === "boolean" ? false : null)]))); const [tools] = declaredTools(d); if (tools?.length || d.app?.mcp) return `{"type": "call_tool", "tool_name": "${tools?.[0] || ""}", "arguments": {}}`; const A = classOf(d, "action"); if (A?.fields?.length) { const lit = (f) => { try { return JSON.parse((f.default || "").replace(/^'(.*)'$/, '"$1"').replace(/^None$/, "null").replace(/^True$/, "true").replace(/^False$/, "false")); } catch { return /int|float/.test(f.type) ? 0 : /bool/.test(f.type) ? false : ""; } }; return JSON.stringify(Object.fromEntries(A.fields.slice(0, 3).map((f) => [f.name, lit(f)]))); } return "{}"; } // ── conformance ────────────────────────────────────────────────────────────── function conformanceSection(me) { const L = me.L; if (!L) return sk.lines(70, 70, 70, 70, 70, 70); const P = probeOf(me); const declared = me.s.declared_openenv || me.s.openenv || me.s.framework === "openenv" || me.s.manifest || D(me).manifest; if (P && !P.openenv && !declared) return `

This server exposes ${esc(P.support?.framework?.label || KIND[P.framework] || "an HTTP API")}. Its published interface is shown above; OpenEnv-specific checks do not apply.

`; const rows = L.running ? L.conformance : seenOf(me)?.conformance; const where = L.running ? `From the probe ${L.checked ? esc(ago(L.checked)) : "just now"}.` : seenOf(me) ? `From when it was last seen running, ${esc(ago(seenOf(me).checked_at))}.` : ""; if (!rows?.length) return `

${L.running ? "Not checked." : "Not seen running here yet: wake it, and its server is checked against these."}

${criteriaList()}`; const host = L.host; return `${P && !P.openenv ? `

This repository has OpenEnv-related metadata, but its server did not return the expected OpenEnv schemas in this probe. The checks below show what answered.

` : ""}

${where} Basic endpoint checks corresponding to openenv validate. Passing does not verify an episode, reward correctness, or training quality.

The MCP endpoint check accepts JSON-RPC error responses. ${P?.mcp?.length ? `Tool discovery returned ${fmt.format(P.mcp.length)} tools; tool execution is untested by this probe.` : `No usable MCP tools were discovered.${P?.mcp_error ? ` Server reply: ${esc(P.mcp_error)}.` : ""}`} OpenEnv environments can use typed actions without MCP tools.

${rows.map((c) => ``).join("")}
CriterionResultWhy
${esc(c.id)}${esc(c.label)}${esc(c.status)}${esc(c.reason)}
${host ? `

Run it yourself:

${snippet(`openenv validate --url ${host}`)}` : ""}`; } const criteriaList = () => `
    ${[["openapi_version_available", "GET /openapi.json returns info.version"], ["health_endpoint", "GET /health returns healthy"], ["metadata_endpoint", "GET /metadata returns name and description"], ["schema_endpoint", "GET /schema returns action, observation and state"], ["mcp_endpoint", "POST /mcp returns JSON-RPC"], ["mode_endpoint_consistency", "its routes match its mode"]].map(([k, v]) => `
  • ${k} ${v}
  • `).join("")}
`; // ── the side panels ────────────────────────────────────────────────────────── function statusPanel(me) { const { s, L } = me; const stage = L?.stage || s.stage || "UNKNOWN"; const [label, tone] = stageOf(stage); let body; if (!L) body = `

${sk.line(70)}

`; else if (stage === "RUNNING") { body = `

${L.checked ? `Checked ${esc(ago(L.checked))}` : "Asking its server what it offers…"}${s.hardware ? ` · ${esc(s.hardware)}` : ""}

`; } else if (stage === "SLEEPING") { body = `

Spaces on free hardware sleep when nobody uses them. Anyone can wake one; it takes a minute or two.${seenOf(me) ? ` Last seen running ${esc(ago(seenOf(me).checked_at))}.` : ""}

`; } else if (STARTING.has(stage)) { const secs = me.started ? Math.round((Date.now() - me.started) / 1000) : null; body = `
  1. Asked to start
  2. ${stage === "BUILDING" ? "Building its image" : "Starting its server"}
  3. Running

${secs != null ? `${secs} s so far. ` : ""}Checking every few seconds.

`; } else { const user = getSession().user; body = `

${stage === "PAUSED" ? "Its owner paused it." : stage === "STOPPED" ? "It was stopped." : "It stopped with an error."} Only someone who may write to it can restart it${user ? "" : ": sign in first"}.

${user ? `` : ``} ${icon("external", 13)}Its logs on the Hub`; } return `

${icon("zap", 14)}Status

${esc(label)}${STARTING.has(stage) ? spinner("xs") : ""}
${body}
`; } function glance(me) { const { s, rank } = me; const rows = [ rank ? ["Trending", `#${fmt.format(rank.n)} of ${fmt.format(rank.of)} Spaces`] : null, ["Likes", esc(s.likes)], s.updated ? ["Updated", esc(ago(Date.parse(s.updated) / 1000))] : null, s.license || D(me).card?.license ? ["License", esc(s.license || D(me).card.license)] : null, ].filter(Boolean); return `

${icon("info", 14)}At a glance

${rows.map(([a, b]) => `
${a}
${b}
`).join("")}
`; } // ── the header ─────────────────────────────────────────────────────────────── function describe(me) { const d = D(me), P = probeOf(me); // OpenEnv's default /metadata says only " environment": not a description const meta = P?.metadata?.description && !/^\w+ environment\.?$/i.test(P.metadata.description.trim()) ? P.metadata.description : null; return d.manifest?.keys?.description || d.discovery?.description || meta || me.s.brief || d.card?.short_description || me.s.heading || ""; } function factsLine(me) { const { s, L } = me, d = D(me), P = probeOf(me); const [label, tone] = stageOf(L?.stage || s.stage); const kind = L?.openenv_verified ? (L.harbor ? "OpenEnv × Harbor" : "OpenEnv · API checked") : P?.framework && !["openenv", "harbor"].includes(P.framework) ? KIND[P.framework] : s.declared_openenv || s.openenv || d.manifest ? "Unverified Space" : "Docker Space"; const mode = P?.mode || (d.dockerfile?.mode ? String(d.dockerfile.mode).toLowerCase() : d.app?.mode ? String(d.app.mode).toLowerCase() : null); const py = d.pyproject && !d.pyproject.error && d.pyproject.openenv ? `${d.pyproject.openenv_package || "openenv-core"} ${d.pyproject.openenv_version || ""}`.trim() : s.openenv_version ? `openenv ${s.openenv_version}` : null; const hw = s.hardware || me.F?.hardware; const bits = [ `${esc(label)}`, `${esc(kind)}`, mode ? `${esc(mode)}` : null, py ? `${esc(py)}` : null, hw ? `${esc(hw)}` : null, me.F && !me.F.error ? `rev ${esc(me.F.short)}` : !me.F ? `${sk.line(100)}` : null, ].filter(Boolean); return bits.join(""); } function linksLine(me) { const { L, spec } = me; return `${icon("external", 13)}Space ${icon("folder", 13)}Files ${L?.running && L.ui ? `${icon("window", 13)}App` : ""} `; } // ── the page ───────────────────────────────────────────────────────────────── const SECTIONS = [ ["interface", "Interface", "gauge"], ["play", "Playground", "play"], ["harbor", "Rollouts here", "box"], ["app", "App", "window"], ["tools", "Tools", "wrench"], ["tasks", "Tasks", "list"], ["rewards", "Rewards", "target"], ["schema", "Schemas", "code"], ["files", "Files", "folder"], ["connect", "Connect", "plug"], ["conformance", "API checks", "check"], ["readme", "README", "doc"], ]; function sectionBody(me, id) { switch (id) { case "interface": return interfaceTable(me); case "play": return playSection(me); case "harbor": return harborSection(me.L); case "app": return appSection(me); case "tools": return toolsSection(me); case "tasks": return tasksSection(me); case "rewards": return rewardsSection(me); case "schema": return schemasSection(me); case "files": return filesSection(me); case "connect": return connectSection(me); case "conformance": return conformanceSection(me); case "readme": return me.s.readme ? `
${md(me.s.readme)}
` : `

No README.

`; default: return ""; } } function noteOf(me, id) { const L = me.L; if (id === "play" && L?.running) return "an episode on the Space"; if (id === "tools" && probeOf(me)?.mcp?.length) return `${fmt.format(probeOf(me).mcp.length)} over MCP`; if (id === "tasks" && L?.running && L.task_api?.splits?.length) return "from its Task API"; if (id === "harbor") return "OpenEnv × Harbor"; return ""; } const sectionsOf = (me) => SECTIONS.filter(([id]) => id !== "harbor" || (me.L?.running && me.L.harbor)); function skeleton(spec) { const [org, name] = spec.split("/"); return `

${ownerLink(org, "/")}${esc(name)}

${sk.line(55, 14)}
${sk.line(40, 13)}

${icon("gauge", 15)}Interface

${ifaceSkeleton()}

${icon("play", 15)}Playground

${sk.box(160)}
`; } export async function mount(el, { spec }) { el.innerHTML = skeleton(spec); const me = page = { spec, s: null, F: null, L: null, el, session: null, log: [], episode: null, poll: null, started: null, taskPage: { split: null, start: 0 }, rank: null, rewards: [], appOpen: false, connectTab: storage.get("sp-ctab") || "agents", filePath: null, viewer: null, rendered: false }; const params = new URLSearchParams(location.search), index = Number(params.get("task")); if (params.has("task") && Number.isSafeInteger(index) && index >= 0) { me.taskPage = { env: params.get("env"), split: params.get("split"), start: Math.floor(index / 20) * 20 }; me.linkedTask = index; } me.onKey = (e) => { if (e.key === "Escape") $(".sp-app.full", el)?.classList.remove("full"); }; document.addEventListener("keydown", me.onKey); el.addEventListener("click", (e) => onClick(me, e)); el.addEventListener("change", (e) => { if (e.target.id === "pg-tool") showTool(me); if (e.target.id === "tk-env") { me.taskPage = { env: e.target.value, split: null, start: 0 }; $("#sb-tasks", me.el).innerHTML = taskBrowser(me); loadTasks(me); } }); // all three at once: the card (and README), what its files declare, what its server says (or said) const card = api(`/api/spaces/${enc(spec)}`); loadFiles(me); loadLive(me, false); api(`/api/search/rank?scope=ready&key=${encodeURIComponent(`space:${spec}`)}`).then((rk) => { // rank among recently checked environment Spaces if (page === me) { me.rank = rk; const g = $("#sp-glance", el); if (g) g.innerHTML = glance(me); } }).catch(() => {}); // not in the catalog yet: no rank shown try { me.s = await card; } catch (e) { if (page !== me) return; el.innerHTML = `
${emptyState("alert", "Couldn't open this Space", esc(e.message), `${icon("arrowLeft", 15)}All Spaces`)}
`; page = null; return; } if (page === me) render(me); } function render(me) { const { s, el, spec } = me; if (!s) return; const [org, name] = spec.split("/"); const sections = sectionsOf(me); const linkedIndex = new URLSearchParams(location.search).get("task"); const taskView = /^\d{1,10}$/.test(linkedIndex || ""); const kind = me.L?.openenv_verified ? "OpenEnv Space" : s.declared_openenv || s.openenv || s.manifest ? "Unverified Space" : s.framework === "ors" ? "ORS Space" : "environment Space"; // as the server-rendered title says if (!new URLSearchParams(location.search).has("task")) setMeta({ title: `${s.heading || name} · ${kind}`, description: `${spec}: an RL environment Space on Hugging Face. ${describe(me)} See it live: its app, a playground with rewards, its tasks, and MCP for coding agents.` }); me.viewer?.destroy(); me.viewer = null; el.innerHTML = `

${taskView ? `Task ${Number(linkedIndex) + 1}` : `${ownerLink(org, "/")}${esc(name)}`}

${taskView ? `

All tasks in ${esc(spec)}

` : ""}

${esc(describe(me))}

${factsLine(me)}
${(s.declared_openenv || s.openenv || s.manifest) && !me.L?.openenv_verified ? `
OpenEnv API not verified. This Space was discovered from repository metadata. A file named openenv.yaml or a Hub tag does not establish compatibility.${me.L?.running ? " The current server did not pass the required API checks." : " It needs a successful check while running."}
` : ""}
${sections.map(([id, title, ic]) => `

${icon(ic, 15)}${esc(title)}

${esc(noteOf(me, id))}
${sectionBody(me, id)}
`).join("")}
`; me.rendered = true; wirePlayground(me); wireApp(me); mountFiles(me); if (me.L?.running && me.L.task_api) loadTasks(me); me.observer?.disconnect(); const links = new Map([...el.querySelectorAll("[data-jump]")].map((a) => [`sec-${a.dataset.jump}`, a])); const seen = new Map(); me.observer = new IntersectionObserver((es) => { // the contents list follows the section in view es.forEach((x) => seen.set(x.target.id, x.isIntersecting)); const first = [...links.keys()].find((id) => seen.get(id)); links.forEach((a, id) => a.classList.toggle("on", id === first)); }, { rootMargin: "-80px 0px -55% 0px" }); el.querySelectorAll(".tp-sec").forEach((x) => me.observer.observe(x)); } // redraw parts in place (the header, the side, some sections), keeping a playground or an open app as they are function paint(me, ids) { if (!me.rendered || page !== me) return; const set = (sel, html) => { const x = $(sel, me.el); if (x) x.innerHTML = html; }; set("#sp-lede", esc(describe(me))); set("#sp-facts", factsLine(me)); set("#sp-links", linksLine(me)); set("#sp-status", statusPanel(me)); set("#sp-glance", glance(me)); for (const id of ids) { if (id === "files" && me.viewer) continue; set(`#sb-${id}`, sectionBody(me, id)); set(`#note-${id}`, esc(noteOf(me, id))); if (id === "files") mountFiles(me); } } async function loadFiles(me) { try { me.F = await api(`/api/spaces/${enc(me.spec)}/files`); } catch (e) { me.F = { error: e.message }; } if (page !== me) return; paint(me, ["interface", "tools", ...(me.L?.running && me.L.task_api?.splits?.length ? [] : ["tasks"]), "rewards", "schema", "files", "connect"]); } async function loadLive(me, fresh) { let L; try { L = await api(`/api/spaces/${enc(me.spec)}/live${fresh ? "?fresh=1" : ""}`); } catch (e) { if (page !== me) return; if (!me.L) me.L = { running: false, stage: me.s?.stage || "UNKNOWN", error: e.message }; const b = $("#sb-interface", me.el); if (b) b.insertAdjacentHTML("beforeend", `
${icon("alert")}Couldn't ask the Space: ${esc(e.message)}
`); if (me.started) schedule(me, 6000); return; } if (page !== me) return; const was = me.L; me.L = L; if (L.task_api && me.indexKey) L.indexKey = me.indexKey; // the page is drawn again only when what the server offers changed (the first answer, or it woke up); otherwise // just the parts that say so, so a playground in progress keeps its episode if (!me.rendered) { /* render() runs when the card arrives and reads me.L */ } else if (!was || was.error || was.running !== L.running || was.openenv_verified !== L.openenv_verified) { const y = scrollY; render(me); scrollTo(0, y); } else if (!L.running && was.stage !== L.stage) paint(me, ["play", "app", "interface", "conformance"]); else paint(me, ["conformance"]); if (STARTING.has(L.stage)) { me.started ||= Date.now(); schedule(me, 4000); } else if (me.started && L.running) { me.started = null; toast("The Space is running"); } } function schedule(me, ms) { clearTimeout(me.poll); me.poll = setTimeout(async () => { if (page !== me) return; if (me.started && Date.now() - me.started > 10 * 60_000) { const t = $("#sp-elapsed", me.el); if (t) t.innerHTML = `Still not up after 10 minutes: check its logs.`; return; } await loadLive(me, true); }, ms); } function mountFiles(me) { const box = $("#sp-files", me.el); if (!box || !me.F || me.F.error || me.viewer) return; const F = me.F; const byPath = new Map(F.files.map((f) => [f.path, f])); const blob = (p) => `https://huggingface.co/spaces/${enc(me.spec)}/blob/${F.sha}/${enc(p)}`; const raw = (p) => `https://huggingface.co/spaces/${enc(me.spec)}/resolve/${F.sha}/${enc(p)}`; const y = scrollY; // the viewer brings its open file's row into view; the page stays where the reader is me.viewer = fileViewer(box, { files: F.files.map((f) => ({ path: f.path, size: f.size, withheld: !!f.withheld })), initial: me.filePath || F.keys?.manifest || F.keys?.readme || null, load: async (p) => { const f = byPath.get(p); if (f?.binary) return { binary: true, size: f.size }; try { return await api(`/api/spaces/${enc(me.spec)}/file?f=${encodeURIComponent(p)}`); } catch (e) { return e.status === 415 ? { binary: true, size: f?.size } : { error: e.message }; } }, hubUrl: blob, rawUrl: (p) => (byPath.get(p)?.binary && /\.(png|jpe?g|gif|webp|svg|avif)$/i.test(p) ? raw(p) : null), onChange: (p) => { me.filePath = p; }, }); if (scrollY !== y) scrollTo({ top: y, behavior: "instant" }); } function openFile(me, path) { const sec = $("#sec-files", me.el); if (!sec) return; sec.scrollIntoView({ behavior: "smooth", block: "start" }); if (me.viewer) me.viewer.open(path); else me.filePath = path; } function wireApp(me) { $(".space-app", me.el)?.addEventListener("load", (e) => e.target.closest(".sp-app-frame")?.classList.add("loaded")); } async function onClick(me, e) { const t = e.target; const j = t.closest("[data-jump]"); if (j) { e.preventDefault(); $(`#sec-${j.dataset.jump}`, me.el)?.scrollIntoView({ behavior: "smooth", block: "start" }); return; } const jt = t.closest("[data-jump-to]"); if (jt) { $(`#sec-${jt.dataset.jumpTo}`, me.el)?.scrollIntoView({ behavior: "smooth", block: "start" }); return; } const of = t.closest("[data-open-file]"); if (of) { e.preventDefault(); openFile(me, of.dataset.openFile); return; } const c = t.closest("[data-copy]"); if (c) { navigator.clipboard?.writeText(c.dataset.copy).then(() => toast("Copied"), () => toast("Couldn't copy")); return; } const act = t.closest("[data-act]")?.dataset.act; if (act === "copy-link") { navigator.clipboard?.writeText(location.href).then(() => toast("Link copied")); return; } if (act === "refresh") { const b = t.closest("button"); b.disabled = true; b.innerHTML = `${spinner()}Checking…`; await loadLive(me, true); return; } if (act === "wake" || act === "restart") { const b = t.closest("button"); const msg = b.parentElement.querySelector(".sp-msg") || $('[data-msg="status"]', me.el); me.el.querySelectorAll(`[data-act="${act}"]`).forEach((x) => { x.disabled = true; x.innerHTML = `${spinner()}${act === "wake" ? "Waking…" : "Restarting…"}`; }); try { const r = await api(`/api/spaces/${enc(me.spec)}/${act}`, { method: "POST", body: {} }); me.started = Date.now(); me.L = { ...(me.L || {}), running: r.stage === "RUNNING", stage: r.stage === "SLEEPING" || ERRORISH(r.stage) ? "APP_STARTING" : r.stage }; paint(me, ["play", "app"]); schedule(me, 3000); } catch (err) { me.el.querySelectorAll(`[data-act="${act}"]`).forEach((x) => { x.disabled = false; x.innerHTML = act === "wake" ? `${icon("power", 14)}Wake it` : `${icon("refresh", 14)}Restart it`; }); if (msg) msg.innerHTML = `
${icon("alert")}${esc(err.message)}${err.status === 403 ? ` Its settings on the Hub` : ""}
`; } return; } const app = t.closest("[data-app]")?.dataset.app; if (app === "load") { me.appOpen = true; $("#sb-app", me.el).innerHTML = appSection(me); wireApp(me); return; } if (app === "reload") { const f = $(".space-app", me.el); if (f) { f.closest(".sp-app-frame")?.classList.remove("loaded"); f.src = f.src; } return; } if (app === "full") { $(".sp-app", me.el)?.classList.toggle("full"); return; } const ct = t.closest("[data-ctab]"); if (ct) { me.connectTab = ct.dataset.ctab; storage.set("sp-ctab", me.connectTab); me.el.querySelectorAll("[data-ctab]").forEach((x) => x.setAttribute("aria-pressed", String(x === ct))); $("#sp-connect", me.el).innerHTML = connectPane(me, me.connectTab); return; } const ag = t.closest("[data-agent]"); if (ag) { storage.set("sp-agent", ag.dataset.agent); me.el.querySelectorAll("[data-agent]").forEach((x) => x.setAttribute("aria-pressed", String(x === ag))); $("#sp-agent", me.el).innerHTML = agentHtml(ag.dataset.agent, agentName(me.spec), `${location.origin}/mcp/${me.spec}`); return; } const tr = t.closest("[data-try]"); if (tr) { e.preventDefault(); const sel = $("#pg-tool", me.el); if (sel) { sel.value = tr.dataset.try; const modes = $(".pg-modes", me.el); if (modes) modes.querySelector('[data-mode="tools"]')?.click(); showTool(me); } $("#sec-play", me.el)?.scrollIntoView({ behavior: "smooth", block: "start" }); return; } const sp = t.closest("[data-split]"); if (sp) { me.taskPage = { env: me.taskPage.env, split: sp.dataset.split, start: 0 }; me.el.querySelectorAll("[data-split]").forEach((x) => x.setAttribute("aria-pressed", String(x === sp))); $("#tk-detail", me.el).hidden = true; loadTasks(me); return; } if (t.closest("#tk-prev")) { me.taskPage.start = Math.max(0, me.taskPage.start - 20); loadTasks(me); return; } if (t.closest("#tk-next")) { me.taskPage.start += 20; loadTasks(me); return; } const play = t.closest("[data-play-task]"); if (play) { const split = $("#pg-split", me.el), idx = $("#pg-index", me.el); if (split) split.value = me.taskPage.split || ""; if (idx) idx.value = play.dataset.playTask; const opts = $(".pg-opts", me.el); if (opts) opts.open = true; $("#sec-play", me.el)?.scrollIntoView({ behavior: "smooth", block: "start" }); await reset(me); return; } const nextTask = t.closest("[data-task-move]"); if (nextTask) { openTask(me, me.selectedTask + Number(nextTask.dataset.taskMove)); return; } if (t.closest("#tk-close")) { $("#tk-detail", me.el).hidden = true; me.el.querySelectorAll("[data-task]").forEach((r) => r.classList.remove("on")); return; } const row = t.closest("[data-task]"); if (row) openTask(me, +row.dataset.task); } const ERRORISH = (x) => /ERROR|STOPPED|PAUSED/.test(x || ""); // ── playground ─────────────────────────────────────────────────────────────── function wirePlayground(me) { const ctl = $(".pg-ctl", me.el); if (!ctl) return; wireForm(ctl); if ($("#pg-tool", me.el)) showTool(me); $("#pg-reset", me.el)?.addEventListener("click", () => reset(me)); $("#pg-step", me.el)?.addEventListener("click", () => step(me)); $("#pg-call", me.el)?.addEventListener("click", () => call(me)); $("#pg-send", me.el)?.addEventListener("click", () => sendRoute(me)); $("#pg-route", me.el)?.addEventListener("change", () => showRoute(me)); if ($("#pg-route", me.el)) showRoute(me); ctl.querySelector(".pg-modes")?.addEventListener("click", (e) => { const b = e.target.closest("[data-mode]"); if (!b) return; ctl.querySelectorAll("[data-mode]").forEach((x) => x.setAttribute("aria-pressed", String(x === b))); ctl.querySelectorAll("[data-pane]").forEach((p) => (p.hidden = p.dataset.pane !== b.dataset.mode)); }); $("#pg-log", me.el)?.addEventListener("click", (e) => { if (e.target.closest("#pg-end")) endSession(me); }); $("#pg-sess", me.el)?.addEventListener("click", (e) => { if (e.target.closest("#pg-end")) endSession(me); }); renderLog(me); } function showTool(me) { const name = $("#pg-tool", me.el)?.value; if (!name) return; const tool = (me.L.mcp || []).find((t) => t.name === name) || {}; $("#pg-desc", me.el).textContent = (tool.description || "").split("\n\n")[0].slice(0, 400); // a server that lists a tool without its input schema: its arguments as JSON (named from its code when we have it) const params = D(me).environment?.tools?.find((t) => t.name === name)?.params || []; $("#pg-form", me.el).innerHTML = tool.inputSchema ? schemaForm(tool.inputSchema, { prefix: "pgt" }) : ``; $("#pg-warn", me.el).innerHTML = name === "run_rollout" ? `` : ""; } function showRoute(me) { const r = me.L.api[+$("#pg-route", me.el).value]; $("#pg-rdesc", me.el).textContent = r.summary || ""; $("#pg-rparams", me.el).innerHTML = r.params.length ? `
${r.params.map((p) => ``).join("")}
` : ""; $("#pg-query", me.el).innerHTML = Object.keys(r.query_schema?.properties || {}).length ? `
Query parameters
${schemaForm(r.query_schema, { prefix: "pgq", skip: [] })}` : ""; $("#pg-rform", me.el).innerHTML = r.method === "POST" ? (r.schema ? schemaForm(r.schema, { prefix: "pgr", skip: [] }) : ``) : ""; } async function sendRoute(me) { const r = me.L.api[+$("#pg-route", me.el).value]; const params = {}; for (const i of me.el.querySelectorAll("[data-rp]")) { if (!/^[\w.\-~]{1,200}$/.test(i.value) || [".", ".."].includes(i.value)) { toast(`${i.dataset.rp}: enter a path value using letters, digits, . - _`); return; } params[i.dataset.rp] = i.value; } let body = null, query = {}; try { if (r.query_schema) query = readForm($("#pg-query", me.el), r.query_schema); } catch (e) { toast(e.message); return; } if (r.method === "POST") { try { if (r.schema) body = readForm($("#pg-rform", me.el), r.schema); else body = JSON.parse($("#pg-rbody", me.el)?.value || "{}"); } catch (e) { toast(e.message || "The body isn't valid JSON"); return; } } await send(me, "http", { method: r.method, path: r.path, params, query, body }, `${r.method} ${r.path}`); } async function ensureSession(me) { if (!me.session) { const generation = me.sessionGeneration || 0; const id = (await api(`/api/spaces/${enc(me.spec)}/play`, { method: "POST", body: { op: "start" } })).session; if (page !== me || generation !== (me.sessionGeneration || 0)) { api(`/api/spaces/${enc(me.spec)}/play`, { method: "POST", body: { op: "end", session: id } }).catch(() => {}); throw new Error("This playground was closed."); } me.session = id; } return me.session; } function endSession(me) { me.sessionGeneration = (me.sessionGeneration || 0) + 1; const id = me.session; me.session = null; me.episode = null; me.log = []; if (id) api(`/api/spaces/${enc(me.spec)}/play`, { method: "POST", body: { op: "end", session: id } }).catch(() => {}); renderLog(me); } async function send(me, op, data, label) { if (me.sending) return; me.sending = true; const entry = { op, label, pending: true }; if (op === "reset") me.log = []; me.log.push(entry); renderLog(me); busy(me, true); const post = () => api(`/api/spaces/${enc(me.spec)}/play`, { method: "POST", body: { op, session: me.session, data } }); try { await ensureSession(me); if (!me.log.includes(entry)) return; let r; try { r = await post(); } catch (err) { if (err.status !== 410 || !me.log.includes(entry)) throw err; me.session = null; // it timed out: a fresh session (an episode in progress is gone, so a step has to start over) if (op !== "reset") { me.episode = null; throw new Error("The session ended. Start a new episode before continuing."); } await ensureSession(me); r = await post(); } if (page !== me || !me.log.includes(entry)) return; Object.assign(entry, { pending: false, result: spaceMedia(r.result, me.L.host), ms: r.ms, stateful: r.stateful }); if (op === "reset") me.episode = { steps: 0, total: 0, done: false, task: taskOf(r.result) }; else { me.episode ||= { steps: 0, total: 0, done: false, task: null }; me.episode.steps++; const rw = rewardOf(op, r.result); if (typeof rw === "number") { me.episode.total += rw; me.rewards.push(rw); paint(me, ["interface", "rewards"]); } if (doneOf(r.result)) me.episode.done = true; } } catch (err) { Object.assign(entry, { pending: false, error: err.message }); } finally { me.sending = false; if (page === me) { busy(me, false); renderLog(me); } } } const rewardOf = (op, r) => (op === "http" ? (typeof r?.json?.reward === "number" ? r.json.reward : null) : op === "call" ? (typeof r?.structuredContent?.reward === "number" ? r.structuredContent.reward : typeof r?.structured_content?.reward === "number" ? r.structured_content.reward : null) : r?.reward ?? r?.observation?.reward); const doneOf = (r) => Boolean(r?.done || r?.observation?.done || r?.terminated || r?.truncated || r?.json?.done || r?.json?.terminated || r?.json?.truncated || r?.structuredContent?.done); const taskOf = (r) => { const o = r?.observation || {}; return o.task_id || o.metadata?.task_id || (o.index != null ? `#${o.index}` : null); }; const busy = (me, on) => me.el.querySelectorAll("#pg-reset, #pg-step, #pg-call, #pg-send, [data-play-task]").forEach((b) => (b.disabled = on)); function resetParams(me) { const out = {}; const raw = $("#pg-params", me.el)?.value.trim(); if (raw) { let p; try { p = JSON.parse(raw); } catch { throw new Error("Reset options aren't valid JSON"); } if (!p || typeof p !== "object" || Array.isArray(p)) throw new Error("Reset options should be a JSON object, like {\"seed\": 1}"); Object.assign(out, p); } const split = $("#pg-split", me.el)?.value, idx = $("#pg-index", me.el)?.value; if (split) out.split = split; if (idx !== "" && idx != null) { if (!Number.isSafeInteger(Number(idx)) || Number(idx) < 0) throw new Error("The task index should be a whole number, 0 or more"); out[me.indexKey || "index"] = Number(idx); // servers name it their own way: the key their Task API rows use } return out; } async function reset(me) { let params; try { params = resetParams(me); } catch (e) { toast(e.message); return; } await send(me, "reset", params, Object.keys(params).length ? JSON.stringify(params) : ""); } async function step(me) { let action; try { action = readForm($("#pg-form", me.el), me.L.schema?.action || {}); } catch (e) { toast(e.message); return; } await send(me, "step", action, JSON.stringify(action).slice(0, 90)); } async function call(me) { const name = $("#pg-tool", me.el).value; const tool = (me.L.mcp || []).find((t) => t.name === name) || {}; if (name === "run_rollout" && !$("#pg-ok", me.el)?.checked) { toast("Tick the box first: this runs on the Space operator's account"); return; } let args; try { if (tool.inputSchema) args = readForm($("#pg-form", me.el), tool.inputSchema); else { args = JSON.parse($("#pg-args", me.el)?.value.trim() || "{}"); if (!args || typeof args !== "object" || Array.isArray(args)) throw new Error("Arguments should be a JSON object"); } } catch (e) { toast(e instanceof SyntaxError ? "The arguments aren't valid JSON" : e.message); return; } await send(me, "call", { name, arguments: args }, `${name}(${JSON.stringify(args).slice(1, -1).slice(0, 80)})`); } function renderLog(me) { const box = $("#pg-log", me.el), sess = $("#pg-sess", me.el); if (!box) return; const ep = me.episode; if (sess) { sess.innerHTML = me.session ? `${ep ? `${ep.task ? `${esc(ep.task)} · ` : ""}${fmt.format(ep.steps)} step${ep.steps === 1 ? "" : "s"}${ep.total ? ` · reward ${esc(String(+ep.total.toFixed(4)))}` : ""}${ep.done ? " · done" : ""}` : "Session open"}` : `No episode yet`; } if (!me.log.length) { box.innerHTML = `
${icon("play", 22)}${me.L?.openenv ? "Start an episode" : me.L?.api?.length ? "Send a request" : "Call a tool"}

Each request shows here with the server's response and any reward it returns.

`; return; } box.innerHTML = me.log.map((x, i) => { const recent = i >= me.log.length - 2; const rw = x.result ? rewardOf(x.op, x.result) : null; const head = `${esc(x.op)}${esc(x.label || "")} ${x.pending ? spinner("xs") : x.error ? `failed` : `${rw != null ? `${esc(String(+Number(rw).toFixed(4)))}` : ""}${fmt.format(x.ms)} ms`}`; const body = x.pending ? `
${x.op === "reset" ? "Starting an episode on the Space…" : "Waiting for the environment…"}
` : x.error ? `
${icon("alert")}${esc(x.error)}
` : x.op === "call" ? callView(x.result) : x.op === "http" ? httpView(x.result) : obsView(x.result); return `
${head}
${body}
`; }).join("") + (me.L?.openenv && me.log.at(-1)?.stateful === false ? `

The WebSocket session is unavailable. These tool calls use HTTP and may not retain episode state.

` : ""); } // ── what the environment sent back ─────────────────────────────────────────── // One status line (reward, done, its feedback), then long text at full width (newlines kept, folded when long), // images, the short fields as a wrapping key–value grid, and nested objects (metadata) as smaller grids. Nothing // scrolls inside the log, and nothing scrolls sideways. const STATUS_KEYS = ["feedback", "message", "status", "result", "error", "info", "action_result"]; const ORDER = ["prompt", "instruction", "question", "task", "observation", "text", "content", "dashboard", "output", "stdout", "stderr"]; const isObj = (v) => v && typeof v === "object" && !Array.isArray(v); const isMedia = (html) => /^<(img|audio|video)\b/.test(html); let foldN = 0; function textBlock(v) { const s = String(v); const looksMd = /(^|\n)(#{1,4} |[-*] |\d+\. |```)/.test(s) && s.length < 40_000; const body = looksMd ? `
${md(s)}
` : `
${esc(s.length > 60_000 ? s.slice(0, 60_000) + "\n… (cut)" : s)}
`; const long = s.length > 1400 || s.split("\n").length > 18; if (!long) return body; const id = `pgv-f${++foldN}`; return `
${body}
`; } function factHtml(k, v, ctx) { if (v == null) return `–`; if (typeof v === "boolean") return `${v}`; if (typeof v === "number") return `${esc(Number.isInteger(v) ? fmt.format(v) : String(+v.toFixed(6)))}`; if (typeof v === "string") { const h = value(v, k, 0, ctx); return h.startsWith("${esc(v)}`; } if (Array.isArray(v)) return v.length ? `${v.map((x) => `${esc(typeof x === "string" ? x : JSON.stringify(x))}`).join("")}` : `none`; return `${Object.entries(v).map(([a, b]) => `${esc(a)} ${b == null ? "–" : esc(typeof b === "object" ? JSON.stringify(b) : String(b))}`).join("")}`; } // an object's fields, sorted into texts, media, short facts and nested groups function fieldsView(o, depth = 0, hide = []) { const texts = [], media = [], facts = [], groups = []; const keys = Object.keys(o).filter((k) => !hide.includes(k)); keys.sort((a, b) => (ORDER.includes(a) ? ORDER.indexOf(a) : 99) - (ORDER.includes(b) ? ORDER.indexOf(b) : 99)); for (const k of keys) { const v = o[k]; if (typeof v === "string") { const h = value(v, k, 0, o); if (isMedia(h)) media.push([k, h]); else if (v.length > 140 || v.includes("\n")) texts.push([k, v]); else facts.push([k, v]); } else if (Array.isArray(v)) { const short = v.length <= 40 && v.every((x) => x == null || ["string", "number", "boolean"].includes(typeof x)) && v.every((x) => String(x).length <= 60); if (short) facts.push([k, v]); else groups.push([k, `
${value(v, k, 1, o)}
`]); } else if (isObj(v)) { const flat = Object.values(v).every((x) => x == null || typeof x !== "object") && Object.keys(v).length <= 8; if (depth > 0 && flat) facts.push([k, v]); else if (Object.keys(v).length) groups.push([k, fieldsView(v, depth + 1)]); else facts.push([k, null]); } else facts.push([k, v]); } return `
${texts.map(([k, v]) => `
${esc(k)}
${textBlock(v)}
`).join("")} ${media.map(([k, h]) => `
${esc(k)}
${h}
`).join("")} ${facts.length ? `
${facts.map(([k, v]) => `
40 || isObj(v) || (Array.isArray(v) && v.length > 4) ? "wide" : ""}">
${esc(k)}
${factHtml(k, v, o)}
`).join("")}
` : ""} ${groups.map(([k, h]) => `
${esc(k)}
${h}
`).join("")}
`; } function statusLine(bits) { const shown = bits.filter(Boolean); return shown.length ? `
${shown.join("")}
` : ""; } // reset/step: {observation, reward, done} function obsView(data) { const obs = isObj(data) && "observation" in data ? data.observation : data; const reward = data?.reward ?? obs?.reward, done = data?.done ?? obs?.done; const said = isObj(obs) ? STATUS_KEYS.find((k) => typeof obs[k] === "string" && obs[k] && obs[k].length <= 240 && !obs[k].includes("\n")) : null; const head = statusLine([ reward != null ? `reward ${esc(typeof reward === "number" ? num(reward) : String(reward))}` : null, done != null ? `${done ? "done" : "not done"}` : null, said ? `${esc(obs[said])}` : null]); if (!isObj(obs)) return head + (obs == null ? "" : typeof obs === "string" ? textBlock(obs) : `
${value(obs, "observation")}
`); return head + fieldsView(obs, 0, ["reward", "done", ...(said ? [said] : [])]); } // an MCP tool's result: its content parts (JSON parts as fields), images, or its raw value function callView(r) { if (!isObj(r) || !Array.isArray(r.content)) return isObj(r) ? fieldsView(r) : `
${value(r)}
`; const sc = r.structuredContent || r.structured_content; const rw = typeof sc?.reward === "number" ? sc.reward : null; const head = statusLine([r.isError || r.is_error ? `error` : null, rw != null ? `reward ${esc(num(rw))}` : null]); const parts = r.content.map((c) => { if (c?.type === "text") { try { const j = JSON.parse(c.text); if (isObj(j)) return fieldsView(j); if (Array.isArray(j)) return `
${value(j)}
`; } catch { /* plain text */ } return textBlock(c.text || ""); } if (c?.type === "image" && /^image\/(png|jpeg|gif|webp)$/.test(c.mimeType || c.mime_type)) return `
`; if (c?.type === "audio") return ``; return `
${value(c)}
`; }).join(""); return head + (parts || `no content`); } // a route's answer: its status, then its body (JSON, text, or an image) function httpView(r) { const ok = r.status >= 200 && r.status < 300; const head = statusLine([`HTTP ${esc(r.status)}`, typeof r.json?.reward === "number" ? `reward ${esc(num(r.json.reward))}` : null]); if (r.image) return head + `
`; if (r.json !== undefined) return head + (isObj(r.json) ? fieldsView(r.json) : `
${value(r.json)}
`); return head + (r.text ? textBlock(r.text) : `no body`); } // ── tasks ──────────────────────────────────────────────────────────────────── async function loadTasks(me) { const L = me.L, box = $("#tk-table", me.el); if (!box || !L?.task_api) return; const splits = taskCatalog(me).splits; me.taskPage.split ??= (splits.find((x) => x.default) || splits[0])?.name || ""; const { split, start, env } = me.taskPage; me.selectedTask = null; $("#tk-detail", me.el).hidden = true; const total = splits.find((x) => x.name === split)?.num_tasks; box.innerHTML = sk.lines(90, 90, 90, 90); $("#tk-range", me.el).textContent = ""; try { const r = await api(`/api/spaces/${enc(me.spec)}/tasks?env=${encodeURIComponent(env)}&split=${encodeURIComponent(split)}&start=${start}&stop=${start + 20}`); if (page !== me || me.taskPage.env !== env || me.taskPage.split !== split || me.taskPage.start !== start) return; me.tasks = r.tasks; if (r.tasks.length && me.indexKey == null) { me.indexKey = "task_index" in r.tasks[0] ? "task_index" : "index"; L.indexKey = me.indexKey; } const keys = columns(r.tasks); box.innerHTML = r.tasks.length ? `
${keys.map((k) => ``).join("")}${r.tasks.map((t, i) => `${keys.map((k) => ``).join("")}`).join("")}
${esc(k)}
${cellOf(t[k])}
${r.withheld.length ? `

${icon("shield", 12)} Left out, as they may hold the answer: ${r.withheld.map((k) => `${esc(k)}`).join(", ")}.

` : ""}` : `

No tasks here.

`; $("#tk-range", me.el).textContent = r.tasks.length ? `${fmt.format(start + 1)}–${fmt.format(start + r.tasks.length)}${total != null ? ` of ${fmt.format(total)}` : ""}` : ""; $("#tk-prev", me.el).disabled = start === 0; $("#tk-next", me.el).disabled = r.tasks.length < 20 || (total != null && start + 20 >= total); box.querySelectorAll("[data-task]").forEach((tr) => tr.addEventListener("keydown", (e) => { if (e.key === "Enter") openTask(me, +tr.dataset.task); })); if (me.linkedTask != null) { const target = me.linkedTask; me.linkedTask = null; openTask(me, target - start); } } catch (e) { box.innerHTML = `
${icon("alert")}${esc(e.message)}
`; } } function columns(rows) { const first = ["task_index", "index", "task_id", "id", "task_name", "name", "title"]; const keys = [...new Set(rows.flatMap((r) => Object.keys(r)))]; const short = keys.filter((k) => rows.every((r) => r[k] == null || (["string", "number", "boolean"].includes(typeof r[k]) && String(r[k]).length <= 80))); const pick = [...first.filter((k) => keys.includes(k)), ...short.filter((k) => !first.includes(k) && !["split", "dataset"].includes(k))].slice(0, 6); return pick.length ? pick : keys.slice(0, 4); } const cellOf = (x) => (x == null ? `–` : typeof x === "string" ? esc(x.length > 80 ? x.slice(0, 80) + "…" : x) : typeof x === "object" ? `${Array.isArray(x) ? `${x.length} items` : "{…}"}` : `${esc(String(x))}`); async function openTask(me, i) { const t = me.tasks?.[i]; if (!t) return; me.selectedTask = i; const requestKey = me.taskRequest = (me.taskRequest || 0) + 1; me.el.querySelectorAll("[data-task]").forEach((r) => r.classList.toggle("on", +r.dataset.task === i)); const idx = t.task_index ?? t.index ?? me.taskPage.start + i; const d = $("#tk-detail", me.el); d.hidden = false; const hub = me.L.harbor_caps?.datasets.find((x) => x.name === me.taskPage.split)?.hub; const rel = hub && typeof t.task_id === "string" && t.task_id.startsWith(me.taskPage.split + "/") ? t.task_id.slice(me.taskPage.split.length + 1) : null; const title = t.task_name || t.title || t.task_id || t.id || `Task ${idx + 1}`; const link = new URL(location.href); link.search = new URLSearchParams({ env: me.taskPage.env, split: me.taskPage.split, task: idx }); link.hash = "sec-tasks"; const canPlay = me.L.openenv && !me.L.harbor && me.taskPage.env === me.L.task_api.env; d.innerHTML = `
Task ${fmt.format(me.taskPage.start + i + 1)} · ${esc(shortSplit(me.taskPage.split))}${esc(title)}
Open task page${copyBtn(link.href, "Copy task link")} ${rel ? `${icon("database", 13)}Open in the explorer` : ""} ${canPlay ? `` : ""}
${taskContent(t)}

Loading the full task…

`; d.scrollIntoView({ block: "nearest", behavior: "smooth" }); const updateTaskMeta = (task) => { const q = new URLSearchParams(location.search); if (q.get("task") === String(idx) && q.get("env") === me.taskPage.env && q.get("split") === me.taskPage.split) { const prompt = [task.prompt, task.instruction, task.description, task.question].find((v) => typeof v === "string") || ""; setMeta({ title: `${title} · ${me.spec}`, description: `${title}: ${me.taskPage.split} task in ${me.spec}, an RL environment on the Hugging Face Hub. ${prompt}` }); const heading = $(".tp-head h1", me.el); if (heading) heading.textContent = title; } }; updateTaskMeta(t); try { const full = await api(`/api/spaces/${enc(me.spec)}/task?${new URLSearchParams({ env: me.taskPage.env, split: me.taskPage.split, index: idx })}`); if (page !== me || requestKey !== me.taskRequest || me.selectedTask !== i || !d.isConnected || d.hidden) return; if (full.task && typeof full.task === "object" && !Array.isArray(full.task)) { $(".tk-detail-b", d).innerHTML = taskContent(full.task); updateTaskMeta(full.task); } else $("#tk-full-status", d).textContent = "Showing the task record returned by the list endpoint."; } catch (error) { if (page === me && requestKey === me.taskRequest && me.selectedTask === i && d.isConnected && !d.hidden) $("#tk-full-status", d).textContent = `Showing the list record. Full task unavailable: ${error.message}`; } }