From a0fd710eff0952ba61532e1416b63f093850d0d5 Mon Sep 17 00:00:00 2001 From: richard-epsilla Date: Sat, 19 Sep 2026 21:18:43 -0700 Subject: [PATCH 01/26] kits: Super Mario, a System One harness plays a platform game in a browser you watch The fifth kit. The harness is a fork of the systemone base with this kit's instructions and one plugin: an MCP server (plugin/bin/mario-env) that opens Full Screen Mario in a headless Chromium inside the deployment, reads the game's own state each step and says it as literal sentences (where Mario is, whether he is on the ground, the nearest enemy, gap and wall in tiles, what one step reaches and what is within it), and acts by holding the keys the game listens to. The keys are sticky: a run continues between decisions and a jump keeps the run key down for the whole flight. Every step writes the browser's frame to frame.jpg in the session workspace, and the page shows it beside a panel that starts, streams, stops and continues the run. Measured on the live model before this commit: 37 actions in 69 s, one death, 47 game seconds played; earlier shapes of the state lost five to seven lives in the same budget (jumping one step late, then braking mid-air). The page-specific code is all in the plugin; the base knows nothing about the game. Credits in the kit's README and on the page: Full Screen Mario at supermarioplay.com (none of its files ship here), Browser Use (MIT), TypeSafe's Jev through OpenRouter. Co-Authored-By: Claude Fable 5.1 --- kits.json | 26 +- kits/mario/README.md | 37 +++ kits/mario/app/app.css | 38 +++ kits/mario/app/app.js | 112 ++++++++ kits/mario/app/icon.svg | 7 + kits/mario/app/index.html | 45 +++ kits/mario/icon.svg | 7 + kits/mario/kit.json | 27 ++ kits/mario/plugin/bin/mario-env | 14 + kits/mario/plugin/mcp.json | 6 + kits/mario/plugin/plugin.json | 8 + kits/mario/plugin/server/mario_env.py | 396 ++++++++++++++++++++++++++ 12 files changed, 718 insertions(+), 5 deletions(-) create mode 100644 kits/mario/README.md create mode 100644 kits/mario/app/app.css create mode 100644 kits/mario/app/app.js create mode 100644 kits/mario/app/icon.svg create mode 100644 kits/mario/app/index.html create mode 100644 kits/mario/icon.svg create mode 100644 kits/mario/kit.json create mode 100755 kits/mario/plugin/bin/mario-env create mode 100644 kits/mario/plugin/mcp.json create mode 100644 kits/mario/plugin/plugin.json create mode 100644 kits/mario/plugin/server/mario_env.py diff --git a/kits.json b/kits.json index d9bc893..19b98b6 100644 --- a/kits.json +++ b/kits.json @@ -6,16 +6,32 @@ "that talks to that Harness as its backend. Launching one provisions the Harness and opens", "the app; there is no separate service to deploy and no database to configure.", "", - "include — build this kit into the image. false leaves it in the repo and out of the", + "include \u2014 build this kit into the image. false leaves it in the repo and out of the", " image, which is how a kit under development stays unshipped.", "", "Everything else a kit needs lives in kits//kit.json, next to the code it describes,", "so adding a kit does not mean editing this file beyond one line." ], "kits": [ - { "name": "slides", "include": true }, - { "name": "sheets", "include": true }, - { "name": "dashboard", "include": true }, - { "name": "video", "include": true } + { + "name": "slides", + "include": true + }, + { + "name": "sheets", + "include": true + }, + { + "name": "dashboard", + "include": true + }, + { + "name": "video", + "include": true + }, + { + "name": "mario", + "include": true + } ] } diff --git a/kits/mario/README.md b/kits/mario/README.md new file mode 100644 index 0000000..cbceb11 --- /dev/null +++ b/kits/mario/README.md @@ -0,0 +1,37 @@ +# Super Mario kit + +A System One model plays a platform game, several decisions a second, and you watch the browser +it plays in. + +## How it is put together + +- **The harness** is a fork of the `systemone` base (the open-source + [System One Harness](https://github.com/HarnessRouter/SystemOneHarness)) with this kit's + instructions and one plugin. +- **The plugin** (`plugin/`) ships an MCP server, `bin/mario-env`, that is the environment: it + opens the game in a headless Chromium inside the deployment, reads the game's own state each step + and says it as short literal sentences (where Mario is, whether he is on the ground, the nearest + enemy, gap and wall with distances in tiles), and acts by holding the keys the game listens to + (`run_right`, `jump_right`, `jump`, `walk_left`, `wait`, each with a hold length). The model never + sees a pixel and never writes text; it chooses. +- **The page** (`app/`) shows `frame.jpg`, which the environment writes into the session's + workspace on every step, so the left pane is the model's own game, not a copy. The right pane + starts a run, streams every action with the game's reply and the step's latency, and can stop or + continue it. + +The base is general and knows nothing about this game; everything about the game lives in this +kit's plugin, which is what a fork of a base is for. + +## Credits + +- The game is **Full Screen Mario**, played at [supermarioplay.com](https://supermarioplay.com/game/mario.html?v=1.0.1). + Mario and its characters belong to Nintendo; this kit plays the page as a person would and ships + none of the game's files. +- The browser is driven with [Browser Use](https://github.com/browser-use/browser-use) (MIT) over the + Chrome DevTools Protocol. +- The model is [Jev](https://typesafe.ai) by TypeSafe, a System One model, reached through OpenRouter. + +## Running it + +Launch the kit from Starter Kits with an OpenRouter key connected. The first launch on a fresh +install adds a Chromium beside the System One base (about a minute). Then press Start. diff --git a/kits/mario/app/app.css b/kits/mario/app/app.css new file mode 100644 index 0000000..63a1617 --- /dev/null +++ b/kits/mario/app/app.css @@ -0,0 +1,38 @@ +:root { color-scheme: light dark; font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; --accent: #E4572E; } +* { box-sizing: border-box; } +body { margin: 0; min-height: 100vh; display: flex; flex-direction: column; } +.top { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; border-bottom: 1px solid #8883; } +.brand { display: flex; align-items: center; gap: 10px; } +.brand img { border-radius: 6px; } +h1 { font-size: 16px; margin: 0; } +.panes { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(320px, 1fr); gap: 0; flex: 1; min-height: 0; } +.stage { padding: 16px; border-right: 1px solid #8883; display: flex; flex-direction: column; gap: 12px; min-width: 0; } +.frame-box { position: relative; width: 100%; aspect-ratio: 16 / 10; background: #101828; border-radius: 10px; overflow: hidden; display: flex; align-items: center; justify-content: center; } +.frame-box img { width: 100%; height: 100%; object-fit: contain; display: block; image-rendering: pixelated; } +.frame-note { color: #cbd5e1; padding: 24px; text-align: center; max-width: 36ch; } +.credits { color: #888; font-size: 12px; margin: 0; } +.credits a { color: inherit; } +.panel { padding: 16px; display: flex; flex-direction: column; gap: 8px; min-width: 0; overflow: auto; } +label { font-weight: 600; margin-top: 4px; } +textarea { width: 100%; font: inherit; padding: 8px; border: 1px solid #8884; border-radius: 8px; background: transparent; color: inherit; resize: vertical; } +button { font: inherit; padding: 7px 14px; border-radius: 8px; border: 1px solid #8886; background: var(--accent); color: #fff; cursor: pointer; } +button[disabled] { opacity: .5; cursor: default; } +button.stop { background: #b91c1c; } +button.quiet { background: transparent; color: inherit; } +.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } +.between { justify-content: space-between; } +.note { color: #888; margin: 0; } +.pill { font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid #8886; } +.pill.ok { border-color: #16a34a; color: #16a34a; } +.pill.running { border-color: var(--accent); color: var(--accent); } +.pill.bad { border-color: #b91c1c; color: #b91c1c; } +ol#steps { padding-left: 20px; margin: 4px 0; } +ol#steps li { margin: 5px 0; } +.action { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; } +.result { color: #777; } +.reason { color: #8a6d00; font-style: italic; } +.final { font-weight: 600; } +@media (max-width: 860px) { + .panes { grid-template-columns: 1fr; } + .stage { border-right: 0; border-bottom: 1px solid #8883; } +} diff --git a/kits/mario/app/app.js b/kits/mario/app/app.js new file mode 100644 index 0000000..2313e8f --- /dev/null +++ b/kits/mario/app/app.js @@ -0,0 +1,112 @@ +// The kit's page is a plain Unified Harness Protocol client, same-origin with the console's API +// proxy: start a run on this kit's harness, stream its items, show the frame the environment +// writes into the session's workspace. Every number shown comes from the server's own events. +const $ = (id) => document.getElementById(id); +const API = '/api/harness/v1'; +const state = { harness: null, responseId: null, sessionId: null, controller: null, startedAt: 0, frameTimer: null }; +const esc = (s) => String(s ?? '').replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' }[c])); + +function setStatus(text, cls) { const s = $('status'); s.textContent = text; s.className = 'pill' + (cls ? ' ' + cls : ''); } +function addStep(html, cls) { + const li = document.createElement('li'); li.className = cls || ''; li.innerHTML = html; $('steps').appendChild(li); + li.scrollIntoView({ block: 'nearest' }); +} + +async function harness() { + if (state.harness) return state.harness; + const r = await fetch(`${API}/harnesses`, { cache: 'no-store' }); + if (!r.ok) throw new Error(`could not list harnesses (${r.status})`); + const { harnesses = [] } = await r.json(); + state.harness = harnesses.find((h) => h.kit === 'mario') || null; + if (!state.harness) throw new Error('Super Mario has not been launched yet. Open Starter Kits and launch it.'); + $('harnessName').textContent = state.harness.name || 'Super Mario'; + $('harnessModel').textContent = state.harness.defaultModel || state.harness.default_model || ''; + return state.harness; +} + +function pollFrame() { + if (!state.sessionId) return; + const img = $('frame'); + const next = () => { state.frameTimer = setTimeout(pollFrame, 180); }; + const url = `${API}/sessions/${encodeURIComponent(state.sessionId)}/files/frame.jpg?t=${Date.now()}`; + const probe = new Image(); + probe.onload = () => { img.src = probe.src; img.hidden = false; $('frameNote').hidden = true; next(); }; + probe.onerror = next; + probe.src = url; +} +function stopFrames() { if (state.frameTimer) clearTimeout(state.frameTimer); state.frameTimer = null; } + +async function start(previous) { + const goal = $('goal').value.trim(); + if (!goal) { $('taskNote').textContent = 'Give the run a goal.'; return; } + let h; + try { h = await harness(); } catch (e) { $('taskNote').textContent = e.message; return; } + const body = { input: goal, stream: true, metadata: { harness_id: h.id } }; + if (previous) body.previous_response_id = previous; + $('steps').innerHTML = ''; $('final').hidden = true; $('traceMeta').textContent = ''; $('taskNote').textContent = ''; + $('startBtn').disabled = true; $('cancelBtn').hidden = false; $('continueBtn').hidden = true; + setStatus('running', 'running'); + state.controller = new AbortController(); state.startedAt = performance.now(); + let calls = 0; + try { + const r = await fetch(`${API}/responses`, { method: 'POST', headers: { 'content-type': 'application/json', accept: 'text/event-stream' }, + body: JSON.stringify(body), signal: state.controller.signal, cache: 'no-store' }); + if (!r.ok) throw new Error(`${r.status} ${(await r.text()).slice(0, 200)}`); + const reader = r.body.getReader(); const dec = new TextDecoder(); let buf = ''; + while (true) { + const { value, done } = await reader.read(); + if (done) break; + buf += dec.decode(value, { stream: true }); + let nl; + while ((nl = buf.indexOf('\n\n')) >= 0) { + const chunk = buf.slice(0, nl); buf = buf.slice(nl + 2); + for (const line of chunk.split('\n')) { + if (!line.startsWith('data: ')) continue; + let ev; try { ev = JSON.parse(line.slice(6)); } catch { continue; } + handle(ev); + } + } + } + } catch (e) { + if (e.name !== 'AbortError') { setStatus('failed', 'bad'); $('taskNote').textContent = e.message; } + } finally { + $('startBtn').disabled = false; $('cancelBtn').hidden = true; + if (state.responseId) $('continueBtn').hidden = false; + setTimeout(stopFrames, 1500); + } + + function handle(ev) { + const t = ev.type; + if (t === 'response.created') { + state.responseId = ev.response.id; + const sid = ev.response.metadata?.session_id || null; + if (sid && sid !== state.sessionId) { state.sessionId = sid; stopFrames(); pollFrame(); } + $('traceTitle').textContent = `Steps · ${state.responseId.slice(0, 12)}…`; + return; + } + if (t === 'response.output_item.done') { + const it = ev.item; const ms = Math.round(performance.now() - state.startedAt); + if (it.type === 'function_call') { calls += 1; addStep(`${esc(it.name)}(${esc(it.arguments)}) ${ms} ms`); } + else if (it.type === 'function_call_output') addStep(`${esc(it.output)}`); + else if (it.type === 'reasoning') addStep(`${esc((it.summary || [])[0]?.text || '')}`); + else if (it.type === 'message') { $('final').textContent = (it.content || [])[0]?.text || ''; $('final').hidden = false; } + return; + } + if (t === 'response.completed' || t === 'response.incomplete' || t === 'response.failed') { + const r = ev.response; const u = r.usage; const secs = ((performance.now() - state.startedAt) / 1000).toFixed(1); + setStatus(r.status + (r.incomplete_details?.reason ? `: ${r.incomplete_details.reason}` : ''), r.status === 'completed' ? 'ok' : 'bad'); + $('traceMeta').textContent = `${calls} action${calls === 1 ? '' : 's'} · ${secs} s` + (u ? ` · ${u.input_tokens} in / ${u.output_tokens} out` : '') + (r.model ? ` · ${r.model}` : ''); + if (r.error) $('taskNote').textContent = r.error.message || JSON.stringify(r.error); + } + } +} + +async function cancel() { + if (!state.responseId) return; + try { await fetch(`${API}/responses/${encodeURIComponent(state.responseId)}/cancel`, { method: 'POST' }); } catch {} +} + +$('startBtn').addEventListener('click', () => start(null)); +$('continueBtn').addEventListener('click', () => start(state.responseId)); +$('cancelBtn').addEventListener('click', cancel); +harness().catch((e) => { $('taskNote').textContent = e.message; }); diff --git a/kits/mario/app/icon.svg b/kits/mario/app/icon.svg new file mode 100644 index 0000000..019c875 --- /dev/null +++ b/kits/mario/app/icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/kits/mario/app/index.html b/kits/mario/app/index.html new file mode 100644 index 0000000..06687d9 --- /dev/null +++ b/kits/mario/app/index.html @@ -0,0 +1,45 @@ + + + + + +Super Mario + + + +
+

Super Mario

+ ready +
+
+
+
+ +
Press Start. The frame of the browser the model plays in appears here, written by the environment on every step.
+
+

The game is Full Screen Mario at supermarioplay.com; Mario belongs to Nintendo and none of the game's files ship here. The browser is driven with Browser Use (MIT). The model is Jev by TypeSafe, a System One model, through OpenRouter. The harness is the open-source System One Harness.

+
+
+
+ Super Mario + +
+ + +
+ + + +
+

+
+ Steps + +
+
    + +
    +
    + + + diff --git a/kits/mario/icon.svg b/kits/mario/icon.svg new file mode 100644 index 0000000..019c875 --- /dev/null +++ b/kits/mario/icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/kits/mario/kit.json b/kits/mario/kit.json new file mode 100644 index 0000000..be82ce2 --- /dev/null +++ b/kits/mario/kit.json @@ -0,0 +1,27 @@ +{ + "id": "mario", + "title": "Super Mario", + "tagline": "A System One model plays a platform game several times a second, and you watch its browser.", + "description": "Full Screen Mario runs in a headless browser inside this deployment. The System One base reads the game's own state each step as a few literal sentences (where Mario is, what is ahead and how far) and chooses one control to hold, a few hundred milliseconds a decision, with no screenshot and no text generation. The left pane shows the frame of the browser the model is playing in; the right pane is the harness. Credits: the game is Full Screen Mario, played at supermarioplay.com; the browser is driven with Browser Use (MIT); the model is TypeSafe's Jev through OpenRouter.", + "icon": "tabler:device-gamepad-2", + "accent": "#E4572E", + "harness": { + "name": "Super Mario", + "base": "systemone", + "recommended": [ + { "base": "systemone", "model": "jev-1.13" }, + { "base": "systemone", "model": "jev-latest" } + ], + "plugin": "plugin", + "mcp_servers": [], + "system_prompt": "You play a side-scrolling platform game as Mario. Each step says where Mario is, whether he is on the ground, and what is ahead with distances in tiles. Keep moving right. Jump over an enemy when it is within 3 tiles, over a gap when its edge is within 2 tiles, and over a pipe or wall when it is within 2 tiles; hold the jump longer for a taller wall or a wider gap. Finish when the level is cleared. Escalate when Mario has no lives left." + }, + "app": { + "route": "/kits/mario" + }, + "session": { + "$comment": "One session is one run of the game. The frame the page shows is frame.jpg in the session's workspace, written by the environment on every step.", + "artifact": "frame.jpg", + "title_from": "meta.title" + } +} diff --git a/kits/mario/plugin/bin/mario-env b/kits/mario/plugin/bin/mario-env new file mode 100755 index 0000000..6b7c362 --- /dev/null +++ b/kits/mario/plugin/bin/mario-env @@ -0,0 +1,14 @@ +#!/bin/sh +# The game environment, started by the runner as the harness's MCP server for a turn. It runs on +# the System One base's own Python (the venv the image installs for that base, which carries +# browser-use and the MCP SDK) and on the Chromium installed beside it; outside the image, any +# python3 with `systemone-harness[browser]` and a Chrome on the machine will do. +here="$(cd "$(dirname "$0")/.." && pwd)" +py=python3 +for cand in /data/agent-tools/systemone-venv/bin/python "$HOME/.systemone-venv/bin/python"; do + if [ -x "$cand" ]; then py="$cand"; break; fi +done +if [ -z "$PLAYWRIGHT_BROWSERS_PATH" ] && [ -d /data/agent-tools/ms-playwright ]; then + export PLAYWRIGHT_BROWSERS_PATH=/data/agent-tools/ms-playwright +fi +exec "$py" "$here/server/mario_env.py" diff --git a/kits/mario/plugin/mcp.json b/kits/mario/plugin/mcp.json new file mode 100644 index 0000000..5df1052 --- /dev/null +++ b/kits/mario/plugin/mcp.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", + "mcpServers": { + "mario": { "type": "stdio", "command": "./bin/mario-env" } + } +} diff --git a/kits/mario/plugin/plugin.json b/kits/mario/plugin/plugin.json new file mode 100644 index 0000000..7bceaab --- /dev/null +++ b/kits/mario/plugin/plugin.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "mario-env", + "version": "0.1.0", + "description": "Full Screen Mario as an environment for the System One base: the game's state as literal text each step, the keys it listens to as actions, and the browser's frame for the page to show.", + "homepage": "https://github.com/HarnessRouter/starter-kit", + "license": "SEE LICENSE IN ../LICENSE.md" +} diff --git a/kits/mario/plugin/server/mario_env.py b/kits/mario/plugin/server/mario_env.py new file mode 100644 index 0000000..a937446 --- /dev/null +++ b/kits/mario/plugin/server/mario_env.py @@ -0,0 +1,396 @@ +"""The game as an environment for a System One harness: an MCP server over a headless Chrome. + +This is the page-specific half of the kit, and the only page-specific code there is. It opens +Full Screen Mario (played at supermarioplay.com) in a browser it controls over the DevTools +Protocol, reads the game's own state each step and says it as short literal sentences (where +Mario is, whether he is on the ground, what is ahead and how far, in tiles), and acts by holding +the keys the game listens to. The System One base compiles these tools into its action space; the +model chooses among them, several times a second, and never sees a pixel. + +Every step also writes the browser's current frame to `frame.jpg` at the workspace root, which +the kit's page shows: what the person watches is the browser the model is playing in. + +State definition convention (systemone_harness/tools.py): `observe` and `reset` are the protocol, +every other tool an action whose parameters are enumerable. The browser is Browser Use's session +(browser-use, MIT) over a Chromium the image installs for the base. +""" + +import asyncio +import glob +import os +import pathlib +import shutil +import sys +import threading +import time +from typing import Annotated + +from pydantic import Field + +GAME_URL = os.environ.get("MARIO_URL", "https://supermarioplay.com/game/mario.html?v=1.0.1") +TILE = 8 # game units per tile; the game draws a tile as 8 * unitsize px +HOLDS = {"short": 0.15, "medium": 0.35, "long": 0.6} +KEY = {"left": 37, "right": 39, "jump": 38, "down": 40, "sprint": 16} +ITEMS = {"Coin", "Mushroom", "FireFlower", "Star", "Vine", "Text", "Shell", "Fireball"} +INSTRUCTIONS = ("You play a side-scrolling platform game as Mario. Each step says where Mario is, whether he is on " + "the ground, and what is ahead with distances in tiles. One step lasts about half a second and " + "covers up to 4 tiles at a run. Keep moving right. The state names any enemy, gap or wall that is " + "within one step: jump over it now, with jump_right; hold the jump longer for a taller wall or a " + "wider gap. Otherwise run right. When Mario has just died, wait. Finish when the level is cleared. " + "Escalate when Mario has no lives left.") + +STATE_JS = """(function(){ + if (!window.player || !window.gamescreen) return {ready: false}; + var u = window.unitsize || 4, T = 8 * u, p = player, sl = gamescreen.left, sr = gamescreen.right; + function tiles(px){ return Math.round(px / T * 10) / 10; } + var enemies = [], gaps = [], walls = []; + (window.characters || []).forEach(function(c){ + if (!c.alive || c === player || c.title === undefined) return; + if (['Coin','Mushroom','FireFlower','Star','Vine','Text','Shell','Fireball'].indexOf(c.title) >= 0) return; + var dx = (c.left - p.right) / T, dy = (p.bottom - c.bottom) / T; + if (dx >= 0 && dx <= 12) enemies.push({kind: c.title, dx: Math.round(dx * 10) / 10, dy: Math.round(dy * 10) / 10}); + }); + var floors = (window.solids || []).filter(function(s){ return s.alive && s.title === 'Floor' && s.top >= p.bottom - 4; }); + var x = p.right, gapStart = null; + for (var i = 0; i <= 12 * T; i += T / 4) { + var covered = floors.some(function(f){ return f.left <= x + i && x + i <= f.right; }); + if (!covered && gapStart === null) gapStart = i; + if (covered && gapStart !== null) { gaps.push({dx: tiles(gapStart), width: tiles(i - gapStart)}); gapStart = null; } + } + if (gapStart !== null) gaps.push({dx: tiles(gapStart), width: tiles(12 * T - gapStart)}); + (window.solids || []).forEach(function(s){ + if (!s.alive || ['Pipe','Block','Brick','Stone'].indexOf(s.title) < 0) return; + var dx = (s.left - p.right) / T; + if (dx >= 0 && dx <= 8 && s.bottom > p.top - T && s.top < p.bottom) { + walls.push({kind: s.title.toLowerCase(), dx: Math.round(dx * 10) / 10, height: tiles(p.bottom - s.top)}); + } + }); + enemies.sort(function(a, b){ return a.dx - b.dx; }); walls.sort(function(a, b){ return a.dx - b.dx; }); gaps.sort(function(a, b){ return a.dx - b.dx; }); + var d = window.data || {}; + function amt(k){ return d[k] && d[k].amount !== undefined ? d[k].amount : null; } + return {ready: true, x: tiles(p.left), y: tiles((window.map && map.floor ? map.floor * u : p.bottom) - p.bottom), + dying: !!p.dying, + screen_tiles: tiles(sr - sl), xvel: Math.round((p.xvel || 0) * 10) / 10, on_ground: !!p.resting, dead: !!p.dead, + power: p.power || 1, enemies: enemies.slice(0, 3), gaps: gaps.slice(0, 2), walls: walls.slice(0, 2), + lives: amt('lives'), time: amt('time'), score: amt('score'), world: amt('world'), paused: !!window.paused, + ending: !!(window.map && map.ending)}; +})()""" + + +def find_chrome() -> str | None: + """A Chromium to launch: the one named, the base's Playwright install, or a system Chrome.""" + for env in ("MARIO_CHROME", "S1_CHROME"): + if os.environ.get(env) and os.path.exists(os.environ[env]): + return os.environ[env] + roots = [os.environ.get("PLAYWRIGHT_BROWSERS_PATH"), "/data/agent-tools/ms-playwright", + os.path.expanduser("~/.cache/ms-playwright")] + for root in roots: + if not root: + continue + for pat in ("chromium-*/chrome-linux*/chrome", "chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium", + "chromium_headless_shell-*/chrome-linux*/headless_shell"): + hits = sorted(glob.glob(os.path.join(root, pat))) + if hits: + return hits[-1] + for p in ("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/usr/bin/google-chrome", + "/usr/bin/chromium", "/usr/bin/chromium-browser"): + if os.path.exists(p): + return p + return shutil.which("google-chrome") or shutil.which("chromium") or None + + +def workspace_root() -> pathlib.Path: + """Where the kit's page reads the frame from: the session workspace, three levels above the + plugin root the runner materialised (.harness/plugins/).""" + root = os.environ.get("PLUGIN_ROOT") + if root: + return pathlib.Path(root).resolve().parents[2] + return pathlib.Path.cwd() + + +class Game: + def __init__(self): + self._loop = asyncio.new_event_loop() + threading.Thread(target=self._loop.run_forever, daemon=True, name="mario-loop").start() + self._session = None + self._cdp = None + self.frame_path = workspace_root() / "frame.jpg" + self.started_at = time.time() + self.steps = 0 + self._lives = None + self._restarting_until = 0.0 + self._held: set[int] = set() + + def _run(self, coro, timeout: float = 120.0): + return asyncio.run_coroutine_threadsafe(coro, self._loop).result(timeout) + + async def _start(self): + if self._session is not None: + return + from browser_use.browser import BrowserProfile, BrowserSession + chrome = find_chrome() + if not chrome: + raise RuntimeError("no Chromium to launch: set PLAYWRIGHT_BROWSERS_PATH or MARIO_CHROME") + profile = BrowserProfile(headless=True, executable_path=chrome, user_data_dir=None, + viewport={"width": 960, "height": 600}, window_size={"width": 960, "height": 600}) + self._session = BrowserSession(browser_profile=profile) + await self._session.start() + await self._session.navigate_to(GAME_URL) + self._cdp = await self._session.get_or_create_cdp_session() + for _ in range(80): + st = await self._eval(STATE_JS) + if isinstance(st, dict) and st.get("ready"): + break + await asyncio.sleep(0.25) + await self._eval("window.unpause && unpause(); 'ok'") + + async def _eval(self, expression: str): + r = await self._cdp.cdp_client.send.Runtime.evaluate(params={"expression": expression, "returnByValue": True}, + session_id=self._cdp.session_id) + return (r.get("result") or {}).get("value") + + async def _frame(self) -> None: + try: + r = await self._cdp.cdp_client.send.Page.captureScreenshot(params={"format": "jpeg", "quality": 45}, + session_id=self._cdp.session_id) + import base64 + data = base64.b64decode(r["data"]) + tmp = self.frame_path.with_suffix(".jpg.tmp") + tmp.write_bytes(data) + os.replace(tmp, self.frame_path) + except Exception: # noqa: BLE001 - a missed frame is a missed frame, never a failed step + pass + + async def _down(self, *codes: int) -> None: + for c in codes: + if c not in self._held: + await self._eval(f"keydown({c}); 'ok'") + self._held.add(c) + + async def _up(self, *codes: int) -> None: + for c in codes: + if c in self._held: + await self._eval(f"keyup({c}); 'ok'") + self._held.discard(c) + + async def _macro(self, name: str, hold: float) -> None: + """The keys stay where a macro leaves them: running stays running between decisions, and a + jump keeps the run key down for the whole flight. Releasing everything after each hold + left Mario braking mid-air and landing short of the enemy he had jumped for (measured: + five deaths in twenty-nine actions with released keys).""" + R, S, U, L = KEY["right"], KEY["sprint"], KEY["jump"], KEY["left"] + if name == "run_right": + await self._up(L) + await self._down(R, S) + await asyncio.sleep(hold) + elif name == "jump_right": + await self._up(L) + await self._down(R, S) + await self._eval(f"keydown({U}); 'ok'") + await asyncio.sleep(hold) + await self._eval(f"keyup({U}); 'ok'") + await asyncio.sleep(max(0.0, 0.75 - hold)) # the flight, run key still down + elif name == "jump": + await self._up(R, S, L) + await self._eval(f"keydown({U}); 'ok'") + await asyncio.sleep(hold) + await self._eval(f"keyup({U}); 'ok'") + await asyncio.sleep(max(0.0, 0.6 - hold)) + elif name == "walk_left": + await self._up(R, S) + await self._down(L) + await asyncio.sleep(hold) + await self._up(L) + else: + await self._up(R, S, L) + await asyncio.sleep(0.25) + + # ── the tools ── + def reset(self, goal: str) -> dict: + async def go(): + await self._start() + await self._session.navigate_to(GAME_URL) + for _ in range(80): + st = await self._eval(STATE_JS) + if isinstance(st, dict) and st.get("ready"): + break + await asyncio.sleep(0.25) + await self._eval("window.unpause && unpause(); 'ok'") + await self._frame() + return {"ok": True} + self.steps = 0 + return self._run(go()) + + def observe(self) -> dict: + async def go(): + await self._start() + st = await self._eval(STATE_JS) + await self._frame() + return st if isinstance(st, dict) else {"ready": False} + st = self._run(go()) + return self._describe(st) + + async def _settled(self) -> dict: + """The state once the level is back after a death: while it restarts the clock is frozen and + the floors are gone (the gap that is not there); an action during that window is spent.""" + st = await self._eval(STATE_JS) + if isinstance(st, dict) and self._restarting(st): + await self._up(*list(self._held)) + for _ in range(20): + await asyncio.sleep(0.25) + st = await self._eval(STATE_JS) + if isinstance(st, dict) and st.get("ready") and st.get("on_ground") and not self._restarting(st): + break + return st if isinstance(st, dict) else {"ready": False} + + def _restarting(self, st: dict) -> bool: + lives = st.get("lives") + if self._lives is not None and lives is not None and lives < self._lives: + self._restarting_until = time.time() + 4.0 + if lives is not None: + self._lives = lives + if st.get("dead") or st.get("dying"): + self._restarting_until = max(self._restarting_until, time.time() + 3.0) + return time.time() < self._restarting_until + + def act(self, name: str, hold: str) -> dict: + seconds = HOLDS.get(hold, HOLDS["medium"]) + + async def go(): + await self._start() + await self._macro(name, seconds) + await asyncio.sleep(0.05) + st = await self._settled() + await self._frame() + return st + st = self._run(go()) + self.steps += 1 + d = self._describe(st) + d["text"] = f"{name} held {seconds:.2f} s. " + d["text"] + return d + + def wait(self) -> dict: + async def go(): + await self._start() + await self._macro("wait", 0.25) + st = await self._settled() + await self._frame() + return st + d = self._describe(self._run(go())) + d["text"] = "Waited 0.25 s. " + d["text"] + return d + + def _describe(self, st: dict) -> dict: + if not st.get("ready"): + return {"ok": True, "text": "The game is loading.", "fields": {"ready": False}, "candidates": {}, "terminal": False} + restarting = self._restarting(st) + if restarting: + lives = st.get("lives") + return {"ok": True, "text": f"Mario has just died. Lives left: {lives}. The level restarts from the beginning in a moment; wait.", + "fields": {"dead": True, "lives": lives, "restarting": True}, "candidates": {}, + "terminal": bool(lives is not None and lives <= 0)} + parts = [] + if st.get("dead"): + parts.append("Mario has just died; the level restarts in a moment.") + else: + motion = ("moving right" if st["xvel"] > 0.3 else "moving left" if st["xvel"] < -0.3 else "standing still") + parts.append(f"Mario is {'on the ground' if st['on_ground'] else 'in the air'} at x {st['x']} tiles of a " + f"{st['screen_tiles']} tile screen, {motion}.") + en = st.get("enemies") or [] + if en: + e = en[0] + where = "at Mario's height" if abs(e["dy"]) < 1 else ("above" if e["dy"] > 0 else "below") + parts.append(f"Nearest enemy: {e['kind']} {e['dx']} tiles ahead, {where}." + (f" {len(en) - 1} more behind it." if len(en) > 1 else "")) + else: + parts.append("No enemy within 12 tiles ahead.") + gaps = st.get("gaps") or [] + parts.append(f"Gap in the ground: edge {gaps[0]['dx']} tiles ahead, {gaps[0]['width']} tiles wide." if gaps + else "No gap in the ground within 12 tiles.") + walls = st.get("walls") or [] + parts.append(f"Wall ahead: {walls[0]['kind']} {walls[0]['dx']} tiles ahead, {walls[0]['height']} tiles tall." if walls + else "No pipe or wall within 8 tiles.") + # what one step reaches, and what is inside it: the fact the rule needs, stated rather + # than left for the model to work out from a speed and a distance (it jumped one step late) + # measured on the live game: at a run the distance to a walking enemy closes about 5.5 + # tiles a step (11.5 -> 6 -> 1.3), a standing wall about 4; the hold, the model's answer + # and the enemy's own walk all fit in one step + reach = round(max(1.0, abs(st.get("xvel") or 0) * 60 * 0.6 / (8 * 4)), 1) + closing = round(reach + 1.5, 1) + within = [f"the {en[0]['kind']} {en[0]['dx']} tiles ahead"] if en and en[0]["dx"] <= closing + 0.5 else [] + within += [f"the gap {gaps[0]['dx']} tiles ahead"] if gaps and gaps[0]["dx"] <= reach + 0.5 else [] + within += [f"the {walls[0]['kind']} {walls[0]['dx']} tiles ahead"] if walls and walls[0]["dx"] <= reach + 0.5 else [] + parts.append(f"One step reaches about {reach} tiles, and a walking enemy closes about {closing}. " + + (f"Within one step: {'; '.join(within)}." if within else "Nothing is within one step.")) + parts.append(f"Lives {st.get('lives')}, time {st.get('time')}, score {st.get('score')}.") + terminal = bool(st.get("ending")) or (bool(st.get("dead")) and (st.get("lives") or 0) <= 0) + fields = {k: st.get(k) for k in ("x", "y", "on_ground", "dead", "xvel", "lives", "time", "score", "world")} + fields["enemies"] = en + fields["gaps"] = gaps + fields["walls"] = walls + fields["step_reach_tiles"] = reach + fields["within_one_step"] = within + return {"ok": True, "text": " ".join(parts), "fields": fields, "candidates": {}, "terminal": terminal} + + +def build(game: Game): + import warnings + warnings.filterwarnings("ignore", message=".*lifespan.*") + from mcp.server.mcpserver import MCPServer + from mcp.types import ToolAnnotations + + m = MCPServer("mario", instructions=INSTRUCTIONS, log_level="WARNING") + Hold = Annotated[str, Field(description="How long to hold the keys.", + json_schema_extra={"oneOf": [{"const": "short", "description": "about 0.15 s, a small step or hop"}, + {"const": "medium", "description": "about 0.35 s, a normal jump or stride"}, + {"const": "long", "description": "about 0.6 s, the highest jump or a long run"}]})] + + @m.tool(annotations=ToolAnnotations(readOnlyHint=True), description="Where Mario is and what is ahead, right now.") + def observe() -> dict: + return game.observe() + + @m.tool(description="Reload the game for a new run.") + def reset(goal: str = "") -> dict: + return game.reset(goal) + + # A key held for a moment changes nothing outside the game, so these are read-risk: gated as + # writes (0.7) the model was refused three times mid-air at 0.40 to 0.59 and the run stopped. + keys = {"risk": "read"} + + @m.tool(description="Run to the right, and keep running until told otherwise.", meta=keys) + def run_right(hold: Hold) -> dict: + return game.act("run_right", hold) + + @m.tool(description="Jump while running right, over an enemy, a gap or a wall ahead; the run continues after the jump.", meta=keys) + def jump_right(hold: Hold) -> dict: + return game.act("jump_right", hold) + + @m.tool(description="Jump straight up, without moving.", meta=keys) + def jump(hold: Hold) -> dict: + return game.act("jump", hold) + + @m.tool(description="Walk to the left, to back away from something.", meta=keys) + def walk_left(hold: Hold) -> dict: + return game.act("walk_left", hold) + + @m.tool(annotations=ToolAnnotations(readOnlyHint=True), description="Let go of every key, stand still for a quarter second and look again.") + def wait() -> dict: + return game.wait() + + return m + + +def main() -> int: + game = Game() + try: + build(game).run("stdio") + finally: + try: + if game._session is not None: + game._run(game._session.kill(), 15) + except Exception: # noqa: BLE001 + pass + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From ae389cf0790eef4acb81eb94c5237ba64f32bec0 Mon Sep 17 00:00:00 2001 From: richard-epsilla Date: Sat, 19 Sep 2026 21:26:17 -0700 Subject: [PATCH 02/26] mario: the panes fill the viewport and the panel scrolls; the list keeps its last 150 items Verified on the public console at 1440 and 390 while a run streamed: the frame stays in view, the panel scrolls inside, Stop cancels the run. Co-Authored-By: Claude Fable 5.1 --- kits/mario/app/app.css | 16 ++++++++++------ kits/mario/app/app.js | 14 ++++++++++---- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/kits/mario/app/app.css b/kits/mario/app/app.css index 63a1617..eb13ac3 100644 --- a/kits/mario/app/app.css +++ b/kits/mario/app/app.css @@ -1,18 +1,20 @@ :root { color-scheme: light dark; font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; --accent: #E4572E; } * { box-sizing: border-box; } -body { margin: 0; min-height: 100vh; display: flex; flex-direction: column; } +html, body { height: 100%; } +body { margin: 0; height: 100vh; display: flex; flex-direction: column; overflow: hidden; } .top { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; border-bottom: 1px solid #8883; } .brand { display: flex; align-items: center; gap: 10px; } .brand img { border-radius: 6px; } h1 { font-size: 16px; margin: 0; } -.panes { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(320px, 1fr); gap: 0; flex: 1; min-height: 0; } -.stage { padding: 16px; border-right: 1px solid #8883; display: flex; flex-direction: column; gap: 12px; min-width: 0; } +.panes { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(320px, 1fr); gap: 0; flex: 1; min-height: 0; overflow: hidden; } +.stage { padding: 16px; border-right: 1px solid #8883; display: flex; flex-direction: column; gap: 12px; min-width: 0; min-height: 0; overflow: auto; } .frame-box { position: relative; width: 100%; aspect-ratio: 16 / 10; background: #101828; border-radius: 10px; overflow: hidden; display: flex; align-items: center; justify-content: center; } .frame-box img { width: 100%; height: 100%; object-fit: contain; display: block; image-rendering: pixelated; } .frame-note { color: #cbd5e1; padding: 24px; text-align: center; max-width: 36ch; } .credits { color: #888; font-size: 12px; margin: 0; } .credits a { color: inherit; } -.panel { padding: 16px; display: flex; flex-direction: column; gap: 8px; min-width: 0; overflow: auto; } +.panel { padding: 16px; display: flex; flex-direction: column; gap: 8px; min-width: 0; min-height: 0; overflow: auto; } +.panel ol#steps { overflow: visible; } label { font-weight: 600; margin-top: 4px; } textarea { width: 100%; font: inherit; padding: 8px; border: 1px solid #8884; border-radius: 8px; background: transparent; color: inherit; resize: vertical; } button { font: inherit; padding: 7px 14px; border-radius: 8px; border: 1px solid #8886; background: var(--accent); color: #fff; cursor: pointer; } @@ -33,6 +35,8 @@ ol#steps li { margin: 5px 0; } .reason { color: #8a6d00; font-style: italic; } .final { font-weight: 600; } @media (max-width: 860px) { - .panes { grid-template-columns: 1fr; } - .stage { border-right: 0; border-bottom: 1px solid #8883; } + .panes { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); } + .stage { border-right: 0; border-bottom: 1px solid #8883; overflow: visible; padding: 10px 12px; } + .stage .credits { display: none; } + .frame-box { aspect-ratio: 16 / 9; } } diff --git a/kits/mario/app/app.js b/kits/mario/app/app.js index 2313e8f..aebae4a 100644 --- a/kits/mario/app/app.js +++ b/kits/mario/app/app.js @@ -8,9 +8,15 @@ const esc = (s) => String(s ?? '').replace(/[&<>]/g, (c) => ({ '&': '&', '<' function setStatus(text, cls) { const s = $('status'); s.textContent = text; s.className = 'pill' + (cls ? ' ' + cls : ''); } function addStep(html, cls) { - const li = document.createElement('li'); li.className = cls || ''; li.innerHTML = html; $('steps').appendChild(li); - li.scrollIntoView({ block: 'nearest' }); + const list = $('steps'); + const li = document.createElement('li'); li.className = cls || ''; li.innerHTML = html; list.appendChild(li); + while (list.children.length > 150) list.removeChild(list.firstChild); // the last 150 items, the run goes on + state.itemsTotal = (state.itemsTotal || 0) + 1; + list.start = state.itemsTotal - list.children.length + 1; // numbering continues past the trim + const panel = list.closest('.panel'); + if (panel) panel.scrollTop = panel.scrollHeight; } +let calls = 0; async function harness() { if (state.harness) return state.harness; @@ -46,8 +52,8 @@ async function start(previous) { $('steps').innerHTML = ''; $('final').hidden = true; $('traceMeta').textContent = ''; $('taskNote').textContent = ''; $('startBtn').disabled = true; $('cancelBtn').hidden = false; $('continueBtn').hidden = true; setStatus('running', 'running'); - state.controller = new AbortController(); state.startedAt = performance.now(); - let calls = 0; + state.controller = new AbortController(); state.startedAt = performance.now(); state.itemsTotal = 0; + calls = 0; try { const r = await fetch(`${API}/responses`, { method: 'POST', headers: { 'content-type': 'application/json', accept: 'text/event-stream' }, body: JSON.stringify(body), signal: state.controller.signal, cache: 'no-store' }); From b4fa314a3fca97086c89f028cbc0e5a6676bbb0c Mon Sep 17 00:00:00 2001 From: richard-epsilla Date: Sat, 19 Sep 2026 22:07:40 -0700 Subject: [PATCH 03/26] mario: the page is the kits' shape, the frame streams, and a tall pipe is jumped The page is rebuilt as the other four kits are built: a Vite app with the shared app bar (with the way back to HarnessRouter), the stage on the left and the run's real conversation on the right in the shared chat column, every decision with the game's reply, reopenable from its link. Play starts a run with the default goal; the stage shows the run's counted figures and Stop. Before the first frame there is an empty state, not a broken image. The frame is Chrome's own screencast now, every third frame the game draws written to frame.jpg (about twenty a second, 7 KB each), read by the page with a few requests in flight and shown as each decodes, instead of one capture per step that played like slides. The environment measures the game instead of guessing: the wall detector keeps the pipe Mario is pressed against (a dx >= 0 filter dropped it, and a run stood at a 4-tile pipe for forty steps saying no wall was within 8 tiles); wall heights are taken from the ground, not from the feet mid-jump; the state says which hold clears the wall (short 3.1 tiles, medium 3.9, long 4.1, measured); and jump_right times the take-off, since a 4-tile pipe is cleared only from 1.5 to 3 tiles back and an enemy jumped from 2 tiles at a run is hit on take-off. On the model's own runs all four pipes of the level are cleared, three times in a row within 120 steps. Co-Authored-By: Claude Fable 5.1 --- kits/mario/README.md | 60 +- kits/mario/app/app.css | 42 - kits/mario/app/app.js | 118 -- kits/mario/app/icon.svg | 7 - kits/mario/app/index.html | 52 +- kits/mario/app/package-lock.json | 1856 ++++++++++++++++++ kits/mario/app/package.json | 20 + kits/mario/app/src/App.jsx | 26 + kits/mario/app/src/components/ChatColumn.jsx | 73 + kits/mario/app/src/components/MkMark.jsx | 12 + kits/mario/app/src/components/Stage.jsx | 102 + kits/mario/app/src/components/Topbar.jsx | 86 + kits/mario/app/src/lib/auth.js | 29 + kits/mario/app/src/lib/frames.js | 78 + kits/mario/app/src/lib/game.js | 54 + kits/mario/app/src/lib/kit.js | 17 + kits/mario/app/src/main.jsx | 12 + kits/mario/app/src/pages/GamePage.jsx | 131 ++ kits/mario/app/src/styles/app.css | 104 + kits/mario/app/src/styles/tokens.css | 107 + kits/mario/app/vite.config.js | 11 + kits/mario/kit.json | 19 +- kits/mario/plugin/server/mario_env.py | 178 +- 23 files changed, 2935 insertions(+), 259 deletions(-) delete mode 100644 kits/mario/app/app.css delete mode 100644 kits/mario/app/app.js delete mode 100644 kits/mario/app/icon.svg create mode 100644 kits/mario/app/package-lock.json create mode 100644 kits/mario/app/package.json create mode 100644 kits/mario/app/src/App.jsx create mode 100644 kits/mario/app/src/components/ChatColumn.jsx create mode 100644 kits/mario/app/src/components/MkMark.jsx create mode 100644 kits/mario/app/src/components/Stage.jsx create mode 100644 kits/mario/app/src/components/Topbar.jsx create mode 100644 kits/mario/app/src/lib/auth.js create mode 100644 kits/mario/app/src/lib/frames.js create mode 100644 kits/mario/app/src/lib/game.js create mode 100644 kits/mario/app/src/lib/kit.js create mode 100644 kits/mario/app/src/main.jsx create mode 100644 kits/mario/app/src/pages/GamePage.jsx create mode 100644 kits/mario/app/src/styles/app.css create mode 100644 kits/mario/app/src/styles/tokens.css create mode 100644 kits/mario/app/vite.config.js diff --git a/kits/mario/README.md b/kits/mario/README.md index cbceb11..bc114be 100644 --- a/kits/mario/README.md +++ b/kits/mario/README.md @@ -7,20 +7,49 @@ it plays in. - **The harness** is a fork of the `systemone` base (the open-source [System One Harness](https://github.com/HarnessRouter/SystemOneHarness)) with this kit's - instructions and one plugin. + instructions and one plugin. The base is general and knows nothing about this game; everything + about the game lives in the plugin, which is what a fork of a base is for. - **The plugin** (`plugin/`) ships an MCP server, `bin/mario-env`, that is the environment: it opens the game in a headless Chromium inside the deployment, reads the game's own state each step and says it as short literal sentences (where Mario is, whether he is on the ground, the nearest - enemy, gap and wall with distances in tiles), and acts by holding the keys the game listens to - (`run_right`, `jump_right`, `jump`, `walk_left`, `wait`, each with a hold length). The model never - sees a pixel and never writes text; it chooses. -- **The page** (`app/`) shows `frame.jpg`, which the environment writes into the session's - workspace on every step, so the left pane is the model's own game, not a copy. The right pane - starts a run, streams every action with the game's reply and the step's latency, and can stop or - continue it. + enemy, gap and wall with distances in tiles, and which hold clears the wall), and acts by holding + the keys the game listens to (`run_right`, `jump_right`, `jump`, `walk_left`, `wait`, each with a + hold length). The model never sees a pixel and never writes text; it chooses. +- **The page** (`app/`) is the same shape as the other kits: the stage on the left streams the + browser the model is playing in, and the conversation on the right is the run itself, every + decision with the game's reply, in the shared chat column the other kits mount. Play starts a + run with the default goal; the composer takes any other. The app bar has the way back to + HarnessRouter. -The base is general and knows nothing about this game; everything about the game lives in this -kit's plugin, which is what a fork of a base is for. +### The frame + +The environment turns on the browser's own screencast and writes every third frame the game +draws to `frame.jpg` in the session's workspace, about twenty a second at 7 KB each. The page reads +that file through the session's live workspace, a few requests in flight at a time, shows a frame +once it has decoded and drops any that arrives after a newer one. The rate the page shows is the +rate of frames it actually displayed. + +### What is measured, not tuned + +The state is literal and the numbers in it come from the game. Two things were measured on the live +game and are written into the environment rather than left to the model: + +- **Jump heights.** Feet above the ground at the apex: a short hold reaches 3.1 tiles, a medium 3.9, + a long 4.1, standing or at a run. So the state says which hold clears the wall ahead: short for + 2 tiles, medium for 3, long for 4. +- **When to leave the ground.** A decision lands every half second and a jump is a one-tile affair. + A wall 4 tiles tall is cleared by a long jump from 1.5 to 3 tiles back and from nowhere else: + from against it the apex is level with the top, and at a run from 4 tiles back the jump peaks + early and hits the side. A running jump covers about 9 tiles, so an enemy jumped from 4 tiles is + landed well past, and one jumped from 2 at a run is hit on take-off. The `jump_right` macro + therefore keeps running until the nearest thing ahead is at its distance, steps back first when + pressed against a tall wall, and waits for the landing when asked to jump mid-air. The model + decides to jump; the timing is the environment's, the way holding the keys is. + +On the model's own runs after these changes (Jev 1.13 through OpenRouter, 120 steps), all four +pipes of the first level are cleared, three times in a row across restarts; before them a run +stood against the first tall pipe for forty steps because the detector had dropped a pipe Mario +overlapped by a tenth of a tile. ## Credits @@ -34,4 +63,13 @@ kit's plugin, which is what a fork of a base is for. ## Running it Launch the kit from Starter Kits with an OpenRouter key connected. The first launch on a fresh -install adds a Chromium beside the System One base (about a minute). Then press Start. +install adds a Chromium beside the System One base (about a minute). Then press Play. + +## Developing the page + +``` +cd kits/mario/app +npm install +npm run dev # against a console at the same origin, or set a proxy for /api and /kits +npm run build # what the image serves at /kits/mario +``` diff --git a/kits/mario/app/app.css b/kits/mario/app/app.css deleted file mode 100644 index eb13ac3..0000000 --- a/kits/mario/app/app.css +++ /dev/null @@ -1,42 +0,0 @@ -:root { color-scheme: light dark; font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; --accent: #E4572E; } -* { box-sizing: border-box; } -html, body { height: 100%; } -body { margin: 0; height: 100vh; display: flex; flex-direction: column; overflow: hidden; } -.top { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; border-bottom: 1px solid #8883; } -.brand { display: flex; align-items: center; gap: 10px; } -.brand img { border-radius: 6px; } -h1 { font-size: 16px; margin: 0; } -.panes { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(320px, 1fr); gap: 0; flex: 1; min-height: 0; overflow: hidden; } -.stage { padding: 16px; border-right: 1px solid #8883; display: flex; flex-direction: column; gap: 12px; min-width: 0; min-height: 0; overflow: auto; } -.frame-box { position: relative; width: 100%; aspect-ratio: 16 / 10; background: #101828; border-radius: 10px; overflow: hidden; display: flex; align-items: center; justify-content: center; } -.frame-box img { width: 100%; height: 100%; object-fit: contain; display: block; image-rendering: pixelated; } -.frame-note { color: #cbd5e1; padding: 24px; text-align: center; max-width: 36ch; } -.credits { color: #888; font-size: 12px; margin: 0; } -.credits a { color: inherit; } -.panel { padding: 16px; display: flex; flex-direction: column; gap: 8px; min-width: 0; min-height: 0; overflow: auto; } -.panel ol#steps { overflow: visible; } -label { font-weight: 600; margin-top: 4px; } -textarea { width: 100%; font: inherit; padding: 8px; border: 1px solid #8884; border-radius: 8px; background: transparent; color: inherit; resize: vertical; } -button { font: inherit; padding: 7px 14px; border-radius: 8px; border: 1px solid #8886; background: var(--accent); color: #fff; cursor: pointer; } -button[disabled] { opacity: .5; cursor: default; } -button.stop { background: #b91c1c; } -button.quiet { background: transparent; color: inherit; } -.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } -.between { justify-content: space-between; } -.note { color: #888; margin: 0; } -.pill { font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid #8886; } -.pill.ok { border-color: #16a34a; color: #16a34a; } -.pill.running { border-color: var(--accent); color: var(--accent); } -.pill.bad { border-color: #b91c1c; color: #b91c1c; } -ol#steps { padding-left: 20px; margin: 4px 0; } -ol#steps li { margin: 5px 0; } -.action { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; } -.result { color: #777; } -.reason { color: #8a6d00; font-style: italic; } -.final { font-weight: 600; } -@media (max-width: 860px) { - .panes { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); } - .stage { border-right: 0; border-bottom: 1px solid #8883; overflow: visible; padding: 10px 12px; } - .stage .credits { display: none; } - .frame-box { aspect-ratio: 16 / 9; } -} diff --git a/kits/mario/app/app.js b/kits/mario/app/app.js deleted file mode 100644 index aebae4a..0000000 --- a/kits/mario/app/app.js +++ /dev/null @@ -1,118 +0,0 @@ -// The kit's page is a plain Unified Harness Protocol client, same-origin with the console's API -// proxy: start a run on this kit's harness, stream its items, show the frame the environment -// writes into the session's workspace. Every number shown comes from the server's own events. -const $ = (id) => document.getElementById(id); -const API = '/api/harness/v1'; -const state = { harness: null, responseId: null, sessionId: null, controller: null, startedAt: 0, frameTimer: null }; -const esc = (s) => String(s ?? '').replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' }[c])); - -function setStatus(text, cls) { const s = $('status'); s.textContent = text; s.className = 'pill' + (cls ? ' ' + cls : ''); } -function addStep(html, cls) { - const list = $('steps'); - const li = document.createElement('li'); li.className = cls || ''; li.innerHTML = html; list.appendChild(li); - while (list.children.length > 150) list.removeChild(list.firstChild); // the last 150 items, the run goes on - state.itemsTotal = (state.itemsTotal || 0) + 1; - list.start = state.itemsTotal - list.children.length + 1; // numbering continues past the trim - const panel = list.closest('.panel'); - if (panel) panel.scrollTop = panel.scrollHeight; -} -let calls = 0; - -async function harness() { - if (state.harness) return state.harness; - const r = await fetch(`${API}/harnesses`, { cache: 'no-store' }); - if (!r.ok) throw new Error(`could not list harnesses (${r.status})`); - const { harnesses = [] } = await r.json(); - state.harness = harnesses.find((h) => h.kit === 'mario') || null; - if (!state.harness) throw new Error('Super Mario has not been launched yet. Open Starter Kits and launch it.'); - $('harnessName').textContent = state.harness.name || 'Super Mario'; - $('harnessModel').textContent = state.harness.defaultModel || state.harness.default_model || ''; - return state.harness; -} - -function pollFrame() { - if (!state.sessionId) return; - const img = $('frame'); - const next = () => { state.frameTimer = setTimeout(pollFrame, 180); }; - const url = `${API}/sessions/${encodeURIComponent(state.sessionId)}/files/frame.jpg?t=${Date.now()}`; - const probe = new Image(); - probe.onload = () => { img.src = probe.src; img.hidden = false; $('frameNote').hidden = true; next(); }; - probe.onerror = next; - probe.src = url; -} -function stopFrames() { if (state.frameTimer) clearTimeout(state.frameTimer); state.frameTimer = null; } - -async function start(previous) { - const goal = $('goal').value.trim(); - if (!goal) { $('taskNote').textContent = 'Give the run a goal.'; return; } - let h; - try { h = await harness(); } catch (e) { $('taskNote').textContent = e.message; return; } - const body = { input: goal, stream: true, metadata: { harness_id: h.id } }; - if (previous) body.previous_response_id = previous; - $('steps').innerHTML = ''; $('final').hidden = true; $('traceMeta').textContent = ''; $('taskNote').textContent = ''; - $('startBtn').disabled = true; $('cancelBtn').hidden = false; $('continueBtn').hidden = true; - setStatus('running', 'running'); - state.controller = new AbortController(); state.startedAt = performance.now(); state.itemsTotal = 0; - calls = 0; - try { - const r = await fetch(`${API}/responses`, { method: 'POST', headers: { 'content-type': 'application/json', accept: 'text/event-stream' }, - body: JSON.stringify(body), signal: state.controller.signal, cache: 'no-store' }); - if (!r.ok) throw new Error(`${r.status} ${(await r.text()).slice(0, 200)}`); - const reader = r.body.getReader(); const dec = new TextDecoder(); let buf = ''; - while (true) { - const { value, done } = await reader.read(); - if (done) break; - buf += dec.decode(value, { stream: true }); - let nl; - while ((nl = buf.indexOf('\n\n')) >= 0) { - const chunk = buf.slice(0, nl); buf = buf.slice(nl + 2); - for (const line of chunk.split('\n')) { - if (!line.startsWith('data: ')) continue; - let ev; try { ev = JSON.parse(line.slice(6)); } catch { continue; } - handle(ev); - } - } - } - } catch (e) { - if (e.name !== 'AbortError') { setStatus('failed', 'bad'); $('taskNote').textContent = e.message; } - } finally { - $('startBtn').disabled = false; $('cancelBtn').hidden = true; - if (state.responseId) $('continueBtn').hidden = false; - setTimeout(stopFrames, 1500); - } - - function handle(ev) { - const t = ev.type; - if (t === 'response.created') { - state.responseId = ev.response.id; - const sid = ev.response.metadata?.session_id || null; - if (sid && sid !== state.sessionId) { state.sessionId = sid; stopFrames(); pollFrame(); } - $('traceTitle').textContent = `Steps · ${state.responseId.slice(0, 12)}…`; - return; - } - if (t === 'response.output_item.done') { - const it = ev.item; const ms = Math.round(performance.now() - state.startedAt); - if (it.type === 'function_call') { calls += 1; addStep(`${esc(it.name)}(${esc(it.arguments)}) ${ms} ms`); } - else if (it.type === 'function_call_output') addStep(`${esc(it.output)}`); - else if (it.type === 'reasoning') addStep(`${esc((it.summary || [])[0]?.text || '')}`); - else if (it.type === 'message') { $('final').textContent = (it.content || [])[0]?.text || ''; $('final').hidden = false; } - return; - } - if (t === 'response.completed' || t === 'response.incomplete' || t === 'response.failed') { - const r = ev.response; const u = r.usage; const secs = ((performance.now() - state.startedAt) / 1000).toFixed(1); - setStatus(r.status + (r.incomplete_details?.reason ? `: ${r.incomplete_details.reason}` : ''), r.status === 'completed' ? 'ok' : 'bad'); - $('traceMeta').textContent = `${calls} action${calls === 1 ? '' : 's'} · ${secs} s` + (u ? ` · ${u.input_tokens} in / ${u.output_tokens} out` : '') + (r.model ? ` · ${r.model}` : ''); - if (r.error) $('taskNote').textContent = r.error.message || JSON.stringify(r.error); - } - } -} - -async function cancel() { - if (!state.responseId) return; - try { await fetch(`${API}/responses/${encodeURIComponent(state.responseId)}/cancel`, { method: 'POST' }); } catch {} -} - -$('startBtn').addEventListener('click', () => start(null)); -$('continueBtn').addEventListener('click', () => start(state.responseId)); -$('cancelBtn').addEventListener('click', cancel); -harness().catch((e) => { $('taskNote').textContent = e.message; }); diff --git a/kits/mario/app/icon.svg b/kits/mario/app/icon.svg deleted file mode 100644 index 019c875..0000000 --- a/kits/mario/app/icon.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/kits/mario/app/index.html b/kits/mario/app/index.html index 06687d9..3846618 100644 --- a/kits/mario/app/index.html +++ b/kits/mario/app/index.html @@ -1,45 +1,13 @@ - - - -Super Mario - - - -
    -

    Super Mario

    - ready -
    -
    -
    -
    - -
    Press Start. The frame of the browser the model plays in appears here, written by the environment on every step.
    -
    -

    The game is Full Screen Mario at supermarioplay.com; Mario belongs to Nintendo and none of the game's files ship here. The browser is driven with Browser Use (MIT). The model is Jev by TypeSafe, a System One model, through OpenRouter. The harness is the open-source System One Harness.

    -
    -
    -
    - Super Mario - -
    - - -
    - - - -
    -

    -
    - Steps - -
    -
      - -
      -
      - - + + + + Super Mario + + + +
      + + diff --git a/kits/mario/app/package-lock.json b/kits/mario/app/package-lock.json new file mode 100644 index 0000000..867255c --- /dev/null +++ b/kits/mario/app/package-lock.json @@ -0,0 +1,1856 @@ +{ + "name": "harnessrouter-kit-mario", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "harnessrouter-kit-mario", + "license": "SEE LICENSE IN ../../LICENSE.md", + "dependencies": { + "lucide-react": "^0.469.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "reifyui": "^0.11.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.0.7" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.4.tgz", + "integrity": "sha512-I+BSHzTAhKN2n7ZwGZsegGcZjDpLqFOMAtJz/u6uFGe0pUFbq56dEHjqJV/ZUdRJtNXNxA+hREUatZBvMR3Oiw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.4.tgz", + "integrity": "sha512-pu3BdjS2LtEzRu2elmGzS3fIeWSZy4BMDIaLNwjorO76+k2d0LMluijhsDx3KQyQBQ/lLUZCQA9/s6csvUfuhw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.4.tgz", + "integrity": "sha512-xfSrj9MHnWK9GaSqT9U0ImHtH/N8WZlHLx4cZHiuLcqs640hvZ3hLPd5UR2AZS57FaE8HrRUSpltbZdWRxHiDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.4.tgz", + "integrity": "sha512-bqU99PLJb/dqb3S0GIMdeuyAEETSUgZBoqXYd3Sd+WCsV+MmPhnN6JrotWyir31+QgH7EvvE5/mwGJlEoci8Fw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.4.tgz", + "integrity": "sha512-JinsFZ5G40oXQb+sUuiA5x689vhr6dDYK0H0NL+rwKdL6CqnmYN8PE4ZwfRSoIjrCxqTQG/SLfTtSvHeGxoVlw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.4.tgz", + "integrity": "sha512-GAdA4UxpiNm27cLHr2GqXBpAD0x9FqwYBY7/YSP0Ss0/PNi4k8gbviqpIpYbVSRBaS2ZcegXEzgTQMbRNCwxCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.4.tgz", + "integrity": "sha512-qDd6NoA1znaLjp4jR5U/KWCdLAKDJNB8W9ChbbDaKbo0xA+Atln5HK6LFCZ4oJQpemtRZA288DCirFRjrspptw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.4.tgz", + "integrity": "sha512-WtB5Tz5KTNINb8ZA+8sQ7bmjuS1JrRT7YverYIhUGdWWDlpzVWmIwuZE+jidkEXUn1l0zrEkaIMa8dHF3NGcsA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.4.tgz", + "integrity": "sha512-VcQ3L1tjnkKzWjryAVaFhHEWcqOfICX9uxVVoDzm2t0DpgKRHd2zOpVrJc0xsWeBZcBFyYROCIBdyR/fS174pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.4.tgz", + "integrity": "sha512-6+ZQX6P5s0cMDN2Ypb8Lbm2+/sZYmZjdaYny992ujUU9UKi/4CWoJWsl1pNvjWJHNHGK51m+jKGLlh1ylb2ifQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.4.tgz", + "integrity": "sha512-D72ZnvkFkBXOfzMMQLcwfPLyGkKb7HZ9/mf97B7v6/P5Lbv4oFOtSY/uHbS8lH6uKUOxoKiuokdb50XZSzzbJw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.4.tgz", + "integrity": "sha512-piU6BxeqA3O9KSu3kRCIQQtNqFFaTu21SEV4FwaRZowpnj3bLaWPZHw+xFqCs0XlJ+aOH3PTRWGoglH+mKA/OA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.4.tgz", + "integrity": "sha512-/5PGpHwqt2EEEOUs1XwzubE/ucr0dWDQ+to3zqi4Ds7EWpwtQ79wXc4JBoxqj/OwpawTsKWzJxHfSuBOq3DrWA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.4.tgz", + "integrity": "sha512-cX3beZDLWt7G2oJF+nhChiT+qtaihs+S2xi7ziGmVB+2pwPng6D0Ed0HmElQOgv2UsUmSJJLGwpBao/3TDx3VA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.4.tgz", + "integrity": "sha512-1uz2mGWHyptR7DgHHrlbdRAjXK7v7elGZ9lMja910/RP+ZYbX6xAmCiU9UZSX4hqmgtHMv6lr5l3kq1HIOpcag==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.4.tgz", + "integrity": "sha512-nLS8topojxyz7SRpKR2IODRpQ0XPZ+xaOXvT3+hqK/Uy8Lo5HFgkkIBiIrCu5tL5YqzTvgovGw55PwpahTAGig==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.4.tgz", + "integrity": "sha512-gs7DRKotr3l3q+jGPQBjH0ng1FjlEDm5ueQrkw5JtQvtLyEIcLASqAEaor56BhkKRzk+IcQzrcanBdb/bBQn8g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.4.tgz", + "integrity": "sha512-791ET7W17NnScOZM7h4dX5hYspxE28htPFsb1awY/NRR8+PRNkS53e475rDdxXXDrP+kwnCcNWg9CX5ztn/Aqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.4.tgz", + "integrity": "sha512-iwZQRcmj7g88g3tzefIrQY7qvmuA/cfYwhrDtTBhsmukO4U2huVO5W+86XacUMRvdSFVAc6kZUZy21JaRwiB9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.4.tgz", + "integrity": "sha512-dVHFp9gRWrdTpnqQuGfCwd7hOQDatK1VCP2iWhLY/cGrOQs/ucFzJ6A5SRqbXX12ZDI8EUuejSM5kwg+ja7Png==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.4.tgz", + "integrity": "sha512-t3NlauOW6gxZVVFcBEnO62Cb4wbyDFL416gTg1uFI/2tgqYQlf69FbSE115Ajre9I+c26Lk4mcmdFUsS/DGifQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.4.tgz", + "integrity": "sha512-xWuIaSye5FWZF8+UYtVEcHtRJDN5kN9Kfgxx3Kq8XIov9KSKbc1fiqQCm90SKrgQbUXZelbnUhnlUJmfSE7P9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.4.tgz", + "integrity": "sha512-9ALJJUOg/ZflMJepVo2PlgsGxSaxN7SQ4Z8GoZfVlarWr6r3rkHUNsd/zAio7p4YMtChSMXPionxej4Hkf6CXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.4.tgz", + "integrity": "sha512-blj9z5qx/Pv4WU0W1NMFDB97e0JH5ed+aZGywW8WCvp/NhWX/4PFAq5uu6Q0AebNn+Vo6KzUYDT++JzTT5ojlQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.4.tgz", + "integrity": "sha512-Erx822VRBwLa124shbj+wNXe//BOgMEctDV0m1aqTQdNO1S69DgNUCFKC1RCeZfixs1J31l6igk1ziyXErbigQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.25", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.25.tgz", + "integrity": "sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.29.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz", + "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.23", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.427", + "node-releases": "^2.0.55", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.433", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.433.tgz", + "integrity": "sha512-5lCAbyZBjtmUt/RAGHRqrL2q0oEFRThDAsZHHDn9XHa89Qw7gMYOeSicBTy+AHfvo0r6vwsZvqNJTQIQy1BLzA==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.469.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.469.0.tgz", + "integrity": "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.56", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.56.tgz", + "integrity": "sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reifyui": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/reifyui/-/reifyui-0.11.1.tgz", + "integrity": "sha512-nRERhMi4bTgxol6O0HjwPFuhnECHNe02X8hm9nNmLGLv44DmF2iFYZPJco6QZbCbh+yYOxzWWiC6B9fpw7h3Mw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "echarts": "^5 || ^6", + "highlight.js": "^11", + "mermaid": "^10 || ^11", + "react": ">=18", + "react-dom": ">=18", + "react-file-icon": "^1" + }, + "peerDependenciesMeta": { + "echarts": { + "optional": true + }, + "highlight.js": { + "optional": true + }, + "mermaid": { + "optional": true + }, + "react-file-icon": { + "optional": true + } + } + }, + "node_modules/rollup": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.4.tgz", + "integrity": "sha512-4U0liVayNIoLp3GFl1FcI8561WepLnZ1rqfraGh7S9B3Ur5F9S283y8Futii7RUU2C/97tOBmBy7nYvhoiOpbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.4", + "@rollup/rollup-android-arm64": "4.63.4", + "@rollup/rollup-darwin-arm64": "4.63.4", + "@rollup/rollup-darwin-x64": "4.63.4", + "@rollup/rollup-freebsd-arm64": "4.63.4", + "@rollup/rollup-freebsd-x64": "4.63.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.4", + "@rollup/rollup-linux-arm-musleabihf": "4.63.4", + "@rollup/rollup-linux-arm64-gnu": "4.63.4", + "@rollup/rollup-linux-arm64-musl": "4.63.4", + "@rollup/rollup-linux-loong64-gnu": "4.63.4", + "@rollup/rollup-linux-loong64-musl": "4.63.4", + "@rollup/rollup-linux-ppc64-gnu": "4.63.4", + "@rollup/rollup-linux-ppc64-musl": "4.63.4", + "@rollup/rollup-linux-riscv64-gnu": "4.63.4", + "@rollup/rollup-linux-riscv64-musl": "4.63.4", + "@rollup/rollup-linux-s390x-gnu": "4.63.4", + "@rollup/rollup-linux-x64-gnu": "4.63.4", + "@rollup/rollup-linux-x64-musl": "4.63.4", + "@rollup/rollup-openbsd-x64": "4.63.4", + "@rollup/rollup-openharmony-arm64": "4.63.4", + "@rollup/rollup-win32-arm64-msvc": "4.63.4", + "@rollup/rollup-win32-ia32-msvc": "4.63.4", + "@rollup/rollup-win32-x64-gnu": "4.63.4", + "@rollup/rollup-win32-x64-msvc": "4.63.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/kits/mario/app/package.json b/kits/mario/app/package.json new file mode 100644 index 0000000..778bc29 --- /dev/null +++ b/kits/mario/app/package.json @@ -0,0 +1,20 @@ +{ + "name": "harnessrouter-kit-mario", + "private": true, + "license": "SEE LICENSE IN ../../LICENSE.md", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "dependencies": { + "lucide-react": "^0.469.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "reifyui": "^0.11.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.0.7" + } +} diff --git a/kits/mario/app/src/App.jsx b/kits/mario/app/src/App.jsx new file mode 100644 index 0000000..aaa42e7 --- /dev/null +++ b/kits/mario/app/src/App.jsx @@ -0,0 +1,26 @@ +// Hash router. +// #/ a new run: nothing exists until the first message opens a session +// #/r/{id} a run (a session on this kit's harness), with its conversation and its frame +// +// The console authenticates the person before this app is served, so there is no sign-in here +// and no token to refresh. See lib/auth.js. +import { useEffect, useState } from 'react'; +import { DialogHost } from 'reifyui'; +import { GamePage } from './pages/GamePage'; +import { PENDING } from './lib/game'; + +function parseHash() { + const raw = window.location.hash.replace(/^#\/?/, ''); + if (raw.startsWith('r/')) return { id: decodeURIComponent(raw.slice(2).split('?')[0]) }; + return { id: PENDING }; +} + +export default function App() { + const [route, setRoute] = useState(parseHash); + useEffect(() => { + const onHash = () => setRoute(parseHash()); + window.addEventListener('hashchange', onHash); + return () => window.removeEventListener('hashchange', onHash); + }, []); + return ; +} diff --git a/kits/mario/app/src/components/ChatColumn.jsx b/kits/mario/app/src/components/ChatColumn.jsx new file mode 100644 index 0000000..29dca9b --- /dev/null +++ b/kits/mario/app/src/components/ChatColumn.jsx @@ -0,0 +1,73 @@ +// The run's conversation: the goal the person gave, every decision the model made with the game's +// reply, and the closing sentence. The panel is reifyui's ChatPanel, the same one the other kits +// mount; what is left here is the two functions that reach the backend and the words on screen. +import { useCallback } from 'react'; +import { Gamepad2 } from 'lucide-react'; +import { ChatPanel } from 'reifyui'; +import { streamTurn, turnsToMessages } from 'reifyui/harness'; +import { isPending, sessionTurns } from '../lib/game'; + +const TOOL_LABELS = { + run_right: 'Run right', + jump_right: 'Jump right', + jump: 'Jump', + walk_left: 'Walk left', + wait: 'Wait', + observe: 'Look', + reset: 'Open the game', +}; + +export function ChatColumn({ runId, seed, onSeedConsumed, handlers, externalBusy, onSessionStarted, + width, collapsed, onToggle }) { + const runTurn = useCallback(({ sessionId, text, handlers: panel }) => streamTurn({ + sessionId: isPending(sessionId) ? '' : sessionId, + input: text, + // the page counts the run from the same events the panel renders + handlers: { + ...panel, + onCreated: (rid) => { handlers.onCreated?.(rid); panel.onCreated?.(rid); }, + onToolCall: (name, args, callId) => { handlers.onToolCall?.(name, args, callId); panel.onToolCall?.(name, args, callId); }, + onToolResult: (callId, output) => { handlers.onToolResult?.(callId, output); panel.onToolResult?.(callId, output); }, + onDone: (status, response) => { handlers.onDone?.(status, response); panel.onDone?.(status, response); }, + onError: (message) => { handlers.onError?.(message); panel.onError?.(message); }, + }, + }), [handlers]); + + const loadHistory = useCallback((sessionId) => ( + isPending(sessionId) ? Promise.resolve([]) : sessionTurns(sessionId).then(turnsToMessages) + ), []); + + return ( + +