From f11fa35a02dd426fd6d2bcc9e7fdaa4f299768ae Mon Sep 17 00:00:00 2001 From: Lightid0234 Date: Fri, 24 Jul 2026 13:21:36 +0100 Subject: [PATCH] Build Agent Arena local beta --- Projects/3_Adversarial Search/.env.example | 8 + Projects/3_Adversarial Search/.gitignore | 14 + Projects/3_Adversarial Search/.nvmrc | 1 + Projects/3_Adversarial Search/Makefile | 42 + Projects/3_Adversarial Search/README.md | 225 +- .../agent_arena_lab_v7_simple.html | 871 +++ Projects/3_Adversarial Search/alembic.ini | 31 + .../apps/api/agent_arena_api/__init__.py | 1 + .../apps/api/agent_arena_api/api.py | 255 + .../api/agent_arena_api/benchmark_seed.py | 73 + .../apps/api/agent_arena_api/cli.py | 77 + .../apps/api/agent_arena_api/competition.py | 339 ++ .../apps/api/agent_arena_api/config.py | 25 + .../apps/api/agent_arena_api/database.py | 33 + .../apps/api/agent_arena_api/main.py | 39 + .../apps/api/agent_arena_api/models.py | 83 + .../apps/api/agent_arena_api/schemas.py | 142 + .../apps/api/agent_arena_api/seed.py | 82 + .../apps/api/agent_arena_api/submissions.py | 307 ++ .../apps/api/alembic/env.py | 39 + .../api/alembic/versions/0001_initial_beta.py | 96 + .../apps/web/eslint.config.js | 25 + .../3_Adversarial Search/apps/web/index.html | 13 + .../apps/web/package-lock.json | 4679 +++++++++++++++++ .../apps/web/package.json | 39 + .../apps/web/src/App.test.tsx | 267 + .../3_Adversarial Search/apps/web/src/App.tsx | 786 +++ .../3_Adversarial Search/apps/web/src/api.ts | 54 + .../web/src/components/ReplayBoard.test.tsx | 69 + .../apps/web/src/components/ReplayBoard.tsx | 140 + .../apps/web/src/main.tsx | 11 + .../apps/web/src/styles.css | 273 + .../apps/web/src/test/setup.ts | 19 + .../apps/web/src/types.ts | 119 + .../apps/web/tsconfig.json | 20 + .../apps/web/vite.config.ts | 27 + .../packages/agent_runner/Dockerfile | 9 + .../agent_runner/agent_runner/__init__.py | 11 + .../agent_runner/agent_runner/compat.py | 47 + .../agent_runner/agent_runner/docker.py | 220 + .../agent_runner/agent_runner/worker.py | 102 + .../isolation_engine/__init__.py | 57 + .../isolation_engine/benchmark.py | 328 ++ .../isolation_engine/match.py | 225 + .../isolation_engine/providers.py | 97 + .../isolation_engine/state.py | 154 + Projects/3_Adversarial Search/pyproject.toml | 54 + .../3_Adversarial Search/scripts/benchmark.py | 48 + Projects/3_Adversarial Search/scripts/dev.py | 81 + .../scripts/startup_check.py | 125 + .../tests/backend/test_benchmark.py | 64 + .../tests/backend/test_dev_script.py | 12 + .../tests/backend/test_engine.py | 120 + .../tests/backend/test_operations.py | 72 + .../tests/backend/test_runner_compat.py | 110 + .../3_Adversarial Search/tests/conftest.py | 30 + .../tests/integration/test_api.py | 160 + .../tests/integration/test_docker_smoke.py | 26 + 58 files changed, 11370 insertions(+), 106 deletions(-) create mode 100644 Projects/3_Adversarial Search/.env.example create mode 100644 Projects/3_Adversarial Search/.gitignore create mode 100644 Projects/3_Adversarial Search/.nvmrc create mode 100644 Projects/3_Adversarial Search/Makefile create mode 100644 Projects/3_Adversarial Search/agent_arena_lab_v7_simple.html create mode 100644 Projects/3_Adversarial Search/alembic.ini create mode 100644 Projects/3_Adversarial Search/apps/api/agent_arena_api/__init__.py create mode 100644 Projects/3_Adversarial Search/apps/api/agent_arena_api/api.py create mode 100644 Projects/3_Adversarial Search/apps/api/agent_arena_api/benchmark_seed.py create mode 100644 Projects/3_Adversarial Search/apps/api/agent_arena_api/cli.py create mode 100644 Projects/3_Adversarial Search/apps/api/agent_arena_api/competition.py create mode 100644 Projects/3_Adversarial Search/apps/api/agent_arena_api/config.py create mode 100644 Projects/3_Adversarial Search/apps/api/agent_arena_api/database.py create mode 100644 Projects/3_Adversarial Search/apps/api/agent_arena_api/main.py create mode 100644 Projects/3_Adversarial Search/apps/api/agent_arena_api/models.py create mode 100644 Projects/3_Adversarial Search/apps/api/agent_arena_api/schemas.py create mode 100644 Projects/3_Adversarial Search/apps/api/agent_arena_api/seed.py create mode 100644 Projects/3_Adversarial Search/apps/api/agent_arena_api/submissions.py create mode 100644 Projects/3_Adversarial Search/apps/api/alembic/env.py create mode 100644 Projects/3_Adversarial Search/apps/api/alembic/versions/0001_initial_beta.py create mode 100644 Projects/3_Adversarial Search/apps/web/eslint.config.js create mode 100644 Projects/3_Adversarial Search/apps/web/index.html create mode 100644 Projects/3_Adversarial Search/apps/web/package-lock.json create mode 100644 Projects/3_Adversarial Search/apps/web/package.json create mode 100644 Projects/3_Adversarial Search/apps/web/src/App.test.tsx create mode 100644 Projects/3_Adversarial Search/apps/web/src/App.tsx create mode 100644 Projects/3_Adversarial Search/apps/web/src/api.ts create mode 100644 Projects/3_Adversarial Search/apps/web/src/components/ReplayBoard.test.tsx create mode 100644 Projects/3_Adversarial Search/apps/web/src/components/ReplayBoard.tsx create mode 100644 Projects/3_Adversarial Search/apps/web/src/main.tsx create mode 100644 Projects/3_Adversarial Search/apps/web/src/styles.css create mode 100644 Projects/3_Adversarial Search/apps/web/src/test/setup.ts create mode 100644 Projects/3_Adversarial Search/apps/web/src/types.ts create mode 100644 Projects/3_Adversarial Search/apps/web/tsconfig.json create mode 100644 Projects/3_Adversarial Search/apps/web/vite.config.ts create mode 100644 Projects/3_Adversarial Search/packages/agent_runner/Dockerfile create mode 100644 Projects/3_Adversarial Search/packages/agent_runner/agent_runner/__init__.py create mode 100644 Projects/3_Adversarial Search/packages/agent_runner/agent_runner/compat.py create mode 100644 Projects/3_Adversarial Search/packages/agent_runner/agent_runner/docker.py create mode 100644 Projects/3_Adversarial Search/packages/agent_runner/agent_runner/worker.py create mode 100644 Projects/3_Adversarial Search/packages/isolation_engine/isolation_engine/__init__.py create mode 100644 Projects/3_Adversarial Search/packages/isolation_engine/isolation_engine/benchmark.py create mode 100644 Projects/3_Adversarial Search/packages/isolation_engine/isolation_engine/match.py create mode 100644 Projects/3_Adversarial Search/packages/isolation_engine/isolation_engine/providers.py create mode 100644 Projects/3_Adversarial Search/packages/isolation_engine/isolation_engine/state.py create mode 100644 Projects/3_Adversarial Search/pyproject.toml create mode 100644 Projects/3_Adversarial Search/scripts/benchmark.py create mode 100644 Projects/3_Adversarial Search/scripts/dev.py create mode 100644 Projects/3_Adversarial Search/scripts/startup_check.py create mode 100644 Projects/3_Adversarial Search/tests/backend/test_benchmark.py create mode 100644 Projects/3_Adversarial Search/tests/backend/test_dev_script.py create mode 100644 Projects/3_Adversarial Search/tests/backend/test_engine.py create mode 100644 Projects/3_Adversarial Search/tests/backend/test_operations.py create mode 100644 Projects/3_Adversarial Search/tests/backend/test_runner_compat.py create mode 100644 Projects/3_Adversarial Search/tests/conftest.py create mode 100644 Projects/3_Adversarial Search/tests/integration/test_api.py create mode 100644 Projects/3_Adversarial Search/tests/integration/test_docker_smoke.py diff --git a/Projects/3_Adversarial Search/.env.example b/Projects/3_Adversarial Search/.env.example new file mode 100644 index 00000000..7018c4c3 --- /dev/null +++ b/Projects/3_Adversarial Search/.env.example @@ -0,0 +1,8 @@ +# Copy to .env for local overrides. Keep this application on a trusted machine. +AGENT_ARENA_DATABASE_URL=sqlite:///var/agent_arena.db +AGENT_ARENA_SOURCE_ROOT=var/sources +AGENT_ARENA_TEMP_ROOT=var/tmp +AGENT_ARENA_RUNNER_IMAGE=agent-arena-runner:py312-v1 +AGENT_ARENA_MOVE_BUDGET_MS=150 +AGENT_ARENA_MINIMUM_RANKED_FIXTURES=10 +AGENT_ARENA_DEMO_OWNER_NAME=oualid diff --git a/Projects/3_Adversarial Search/.gitignore b/Projects/3_Adversarial Search/.gitignore new file mode 100644 index 00000000..05c8f58e --- /dev/null +++ b/Projects/3_Adversarial Search/.gitignore @@ -0,0 +1,14 @@ +.env +.venv/ +__pycache__/ +*.py[cod] +*.egg-info/ +.pytest_cache/ +.ruff_cache/ +.coverage +htmlcov/ +apps/web/node_modules/ +apps/web/dist/ +apps/web/*.tsbuildinfo +docs/ +var/ diff --git a/Projects/3_Adversarial Search/.nvmrc b/Projects/3_Adversarial Search/.nvmrc new file mode 100644 index 00000000..fc37597b --- /dev/null +++ b/Projects/3_Adversarial Search/.nvmrc @@ -0,0 +1 @@ +22.17.0 diff --git a/Projects/3_Adversarial Search/Makefile b/Projects/3_Adversarial Search/Makefile new file mode 100644 index 00000000..c1e8ab2d --- /dev/null +++ b/Projects/3_Adversarial Search/Makefile @@ -0,0 +1,42 @@ +PYTHON ?= python3.12 +VENV ?= .venv + +.PHONY: setup dev benchmark test seed run-round runner-build format lint clean-start + +setup: + $(PYTHON) -m venv $(VENV) + $(VENV)/bin/python -m pip install --upgrade pip + $(VENV)/bin/python -m pip install -e ".[dev]" + cd apps/web && npm ci + $(VENV)/bin/alembic upgrade head + $(VENV)/bin/agent-arena seed + +dev: + $(VENV)/bin/python scripts/dev.py + +benchmark: + $(VENV)/bin/python scripts/benchmark.py + +test: + $(VENV)/bin/ruff format --check apps packages scripts tests + $(VENV)/bin/ruff check apps packages scripts tests + $(VENV)/bin/pytest + cd apps/web && npm run lint && npm run typecheck && npm test -- --run && npm run build + +seed: + $(VENV)/bin/agent-arena seed + +run-round: + $(VENV)/bin/agent-arena run-round + +runner-build: + docker build -f packages/agent_runner/Dockerfile -t agent-arena-runner:py312-v1 . + +format: + $(VENV)/bin/ruff format apps packages tests + +lint: + $(VENV)/bin/ruff check apps packages tests + +clean-start: + $(VENV)/bin/python scripts/startup_check.py diff --git a/Projects/3_Adversarial Search/README.md b/Projects/3_Adversarial Search/README.md index e21dff4f..1d010f76 100644 --- a/Projects/3_Adversarial Search/README.md +++ b/Projects/3_Adversarial Search/README.md @@ -1,159 +1,172 @@ +# Agent Arena -# Build an Adversarial Game Playing Agent +Agent Arena is a local browser-based beta for one permanent Knight Isolation competition. -![Example game of isolation on a square board](viz.gif) +> Submit once. Your Knight Isolation agent keeps competing. -## Synopsis +The beta deliberately uses a small architecture: React calls FastAPI over HTTP; FastAPI uses SQLAlchemy with one SQLite file; the authoritative Python engine runs games; and submitted source is imported only inside a Docker container. Rankings are calculated directly from completed games instead of being cached in a separate standings table. -In this project, you will experiment with adversarial search techniques by building an agent to play knights Isolation. Unlike the examples in lecture where the players control tokens that move like chess queens, this version of Isolation gives each agent control over a single token that moves in L-shaped movements--like a knight in chess. +## What the product does -### Isolation +- Edit and practice one `agent.py` against Random, Greedy, or Minimax. +- Validate syntax, the public contract, representative legal actions, and one Greedy scrimmage. +- Store only accepted immutable submissions. +- Automatically make the newest accepted submission active. +- Run one mirrored fixture for every pair of active submissions. +- Award 3/1/0 fixture points and rank qualified agents by points per fixture. +- Replay both games in a completed fixture. +- Open a subordinate Agent page for immutable submissions and complete fixture history. -In the game Isolation, two players each control their own single token and alternate taking turns moving the token from one cell to another on a rectangular grid. Whenever a token occupies a cell, that cell becomes blocked for the remainder of the game. An open cell available for a token to move into is called a "liberty". The first player with no remaining liberties for their token loses the game, and their opponent is declared the winner. +There are five browser views: Overview, Code & Play, Leaderboard, My Agents, and Submit Agent. There is no rating system, calendar, season, event system, payment system, or production authentication. -In knights Isolation, tokens can move to any open cell that is 2-rows and 1-column or 2-columns and 1-row away from their current position on the board. On a blank board, this means that tokens have at most eight liberties surrounding their current location. Token movement is blocked at the edges of the board (the board does not wrap around the edges), however, tokens can "jump" blocked or occupied spaces (just like a knight in chess). +The leaderboard marks an active submission as provisional until it has played 10 fixtures by default. Qualified entries are ordered by points per fixture (PPF), then raw points, fixture wins, and agent name. The threshold can be changed with `AGENT_ARENA_MINIMUM_RANKED_FIXTURES`; the formula and qualification state are returned by the API. -Finally, agents have a fixed time limit (150 milliseconds by default) to search for the best move and respond. The search will be automatically cut off after the time limit expires, and the active agent will forfeit the game if it has not chosen a move. +## Prerequisites -**You can find more information (including implementation details) about the in the Isolation library readme [here](/isolation/README.md).** +- Python 3.12.x +- Node.js 22.17.0 and npm +- Docker Engine or Docker Desktop for submitted-source execution +- GNU Make on Linux/macOS; PowerShell commands are provided for Windows +Docker is optional for browsing seeded results and running trusted baseline rounds. It is mandatory for practice or competition with submitted source. The API never falls back to importing submitted source. -## Getting Started (Workspaces) +## Database reset after the simplification refactor -The easiest way to complete the project is to use the Udacity Workspace in your classroom. The environment has already been configured with the required files and libraries to support the project. If you decide to use the Workspace, then you do NOT need to perform any of the setup steps for this project. Skip to the section with instructions for completing the project. +The simplified schema intentionally replaces the earlier local-beta schema. Existing data is not deleted automatically. +If `var/agent_arena.db` was created by the earlier architecture, rename it before setup: -## Getting Started (Local Environment) - -If you would prefer to complete the exercise in your own local environment, then follow the steps below: - -- Open your terminal and activate the aind conda environment (OS X or Unix/Linux users use the command shown; Windows users only run `activate aind`) -``` -$ source activate aind -``` - -- Download a copy of the project files from GitHub and navigate to the project folder. (Note: if you've previously downloaded the repository for another project then you can skip the clone command. However, you should run `git pull` to receive any project updates since you cloned the repository.) -``` -(aind) $ git clone https://github.com/udacity/artificial-intelligence -(aind) $ cd "artificial-intelligence/Projects/3_Adversarial Search" +```powershell +Move-Item var\agent_arena.db var\agent_arena_before_simplification.db ``` +On Linux/macOS: -## Instructions - -You must implement an agent in the `CustomPlayer` class defined in the `my_custom_player.py` file. The interface definition for game agents only requires you to implement the `.get_action()` method, but you can add any other methods to the class that you deem necessary. You can build a basic agent by combining minimax search with alpha-beta pruning and iterative deepening from lecture. - -**NOTE:** Your agent will **not** be evaluated in an environment suitable for running machine learning or deep learning agents (like AlphaGo); visit an office hours sessions **after** completing the project if you would like guidance on incorporating machine learning in your agent. - -#### The get_action() Method -This function is called once per turn for each player. The calling function handles the time limit and +```bash +mv var/agent_arena.db var/agent_arena_before_simplification.db ``` -def get_action(self, state): - import random - self.queue.put(random.choice(state.actions())) -``` - -- **DO NOT** use multithreading/multiprocessing (the isolation library already uses them, so using them in your agent may cause conflicts) - -#### Initialization Data -Your agent will automatically read the contents of a file named `data.pickle` if it exists in the same folder as `my_custom_player.py`. The serialized object from the pickle file will be assigned to `self.data`. Your agent should not write to or modify the contents of the pickle file during search. -The log file will record a warning message if there is no data file, however a data file is NOT required unless you need it for your opening book. (You are allowed to use the data file to provide _any_ initialization information to your agent; it is not limited to an opening book.) +The old database remains available as a backup but is not read by the simplified application. +## Linux/macOS quick start -#### Saving Information Between Turns -The `CustomPlayer` class can pass internal state by assigning the data to the attribute `self.context`. An instance of your agent class will carry the context between each turn of a single game, but the contents will be reset at the start of any new game. -``` -def get_action(...): - action = self.mcts() - self.queue.put(action) - self.context = object_you_want_to_save # self.context will contain this object on the next turn +```bash +cp .env.example .env +make setup +make runner-build +make dev ``` -## Choose an Experiment +Open: -Select at least one of the following to implement and evaluate in your report. (There is no upper limit on the techniques you incorporate into your agent.) +- Web app: http://127.0.0.1:5173 +- API health: http://127.0.0.1:8000/api/health +- OpenAPI: http://127.0.0.1:8000/docs -### Option 1: Develop a custom heuristic (must not be one of the heuristics from lectures, and cannot only be a combination of the number of liberties available to each agent) +`make setup` creates the virtual environment, installs pinned dependencies, applies the one Alembic migration, and seeds four trusted demo agents plus completed fixtures. -- Create a performance baseline using `run_search.py` (with the `fair_matches` flag enabled) to evaluate the effectiveness of your agent using the #my_moves - #opponent_moves heuristic from lecture -- Use the same process to evaluate the effectiveness of your agent using your own custom heuristic +## Windows PowerShell -**Hints:** -- Research other games (chess, go, connect4, etc.) to get ideas for developing good heuristics -- If the results of your tests are very close, try increasing the number of matches (e.g., >100) to increase your confidence in the results -- Experiment with adding more search time--does adding time confer any advantage to your agent over the baseline? -- Augment the code to count the nubmer of nodes your agent searches--is it better to search more or fewer nodes? How does your heuristic compare to the baseline heuristic you chose? +```powershell +Copy-Item .env.example .env +py -3.12 -m venv .venv +.\.venv\Scripts\python.exe -m pip install -e ".[dev]" +npm.cmd ci --prefix apps\web +.\.venv\Scripts\alembic.exe upgrade head +.\.venv\Scripts\agent-arena.exe seed +docker build -f packages\agent_runner\Dockerfile -t agent-arena-runner:py312-v1 . +.\.venv\Scripts\python.exe scripts\dev.py +``` +This checkout may be inspected with another Python version, but the supported application and runner runtime is Python 3.12. -### Option 2: Develop an opening book (must span at least depth 4 of the search tree) +## Agent contract -- Write your own code to develop an opening book of the best moves for every possible game state from an empty board to at least a depth of 4 plies -- Create a performance baseline using `run_search.py` (with the `fair_matches` flag _disabled_) to evaluate the effectiveness of your agent using randomly chosen opening moves. (You can use any heuristic function, but you should use the same heuristic on your agent for all experiments.) -- Use the same procedure to evaluate the effectiveness of your agent when early moves are selected from your opening book +Submit one file named `agent.py`: -**Hints:** -- Developing an opening book can require long run-times to simulate games and accumulate outcome statistics -- If the results are very close, try increasing the number of matches (e.g., >100) to increase your confidence in the results +```python +class CustomPlayer: + def get_action(self, state): + actions = state.actions() + return actions[0] if actions else None +``` -**Adding a basic opening book** -- You will need to write your own code to develop a good opening book, but you can pass data into your agent by saving the file as "data.pickle" in the same folder as `my_custom_player.py`. Use the [pickle](https://docs.python.org/3/library/pickle.html) module to serialize the object you want to save. The pickled object will be accessible to your agent through the `self.data` attribute. +The original queue style is also supported: -For example, the contents of dictionary `my_data` can be saved to disk: -``` -import pickle -from isolation import Isolation -state = Isolation() -my_data = {state: 57} # opening book always chooses the middle square on an open board -with open("data.pickle", 'wb') as f: - pickle.dump(my_data, f) +```python +self.queue.put(action) ``` +The player may use `self.context` during one game. A fresh container and player instance are created for the next game. The trusted engine always validates the proposed action against `state.actions()`. -### Option 3: Build an agent using advanced search techniques (for example: killer heuristic, principle variation search (not in lecture), or monte carlo tree search (not in lecture)) +## Simple data model -- Create a performance baseline using `run_search.py` to evaluate the effectiveness of a baseline agent (e.g., an agent using your minimax or alpha-beta search code from the classroom) -- Use `run_search.py` to evaluate the effectiveness of your agent using your own custom search techniques -- You must decide whether to test with or without "fair" matches enabled--justify your choice in your report +- **Agent**: stable name and owner identity. +- **Submission**: immutable accepted source; the newest accepted submission is active. +- **Fixture**: one pair of mirrored games. +- **Game**: one result and one canonical replay list. -**Hints:** -- If the results are very close, try increasing the number of matches (e.g., >100) to increase your confidence in the results -- Experiment with adding more search time--does adding time confer any advantage to your agent? -- Augment the code to count the number of nodes your agent searches--does your agent have an advantage compared to the baseline search algorithm you chose? +There is no Standing table. The leaderboard is recalculated from completed Fixtures and Games whenever it is requested. -**Note:** -- You MAY implement advanced techniques from the reading list at the end of the lesson (like Monte Carlo Tree Search, principle variation search, etc.), but your agent is being evaluated for _performance_ rather than _correctness_. It's possible to pass the project requirements **without** using these advanced techniques, so project reviewers may encourage you to implement a simpler solution if you are struggling with correct implementation. (That's good general advice: do the simplest thing first, and only add complexity when you must.) +## Common commands +```bash +make dev # launch the normal development database +make benchmark # launch with the isolated 15-agent benchmark database +make seed # idempotently create demo agents and initial fixtures +make run-round # synchronously run one balanced all-active round +make test # backend and frontend formatting, lint, tests, and build +make clean-start # start temporary API/web ports and probe both +make runner-build # build the local Python 3.12 runner image +``` -## Report Requirements +On Windows, launch benchmark mode with: -Your report must include a table or chart with data from an experiment to evaluate the performance of your agent as described above. Use the data from your experiment to answer the relevant questions below. (You may choose one set of questions if your agent incorporates multiple techniques.) +```powershell +.\.venv\Scripts\python.exe scripts\benchmark.py +``` -**Advanced Heuristic** -- What features of the game does your heuristic incorporate, and why do you think those features matter in evaluating states during search? -- Analyze the search depth your agent achieves using your custom heuristic. Does search speed matter more or less than accuracy to the performance of your heuristic? +Stop the normal development servers first because both modes use ports 5173 and 8000. Benchmark mode applies the same migration and seeds 15 trusted strategy variants into `var/benchmark/agent_arena.db`; it does not read, seed, or modify `var/agent_arena.db`. Open the Leaderboard and click **Run competition round**. One 15-agent round runs 105 mirrored fixtures (210 games), gives every agent 14 fixtures, and therefore clears the default 10-fixture ranking threshold. -**Opening book** -- Describe your process for collecting statistics to build your opening book. How did you choose states to sample? And how did you perform rollouts to determine a winner? -- What opening moves does your book suggest are most effective on an empty board for player 1 and what is player 2's best reply? +The CLI intentionally contains only three commands: -**Advanced Search Techniques** -- Choose a baseline search algorithm for comparison (for example, alpha-beta search with iterative deepening, etc.). How much performance difference does your agent show compared to the baseline? -- Why do you think the technique you chose was more (or less) effective than the baseline? +```bash +agent-arena doctor +agent-arena seed +agent-arena run-round +``` +`doctor` checks whether the database file and Docker runner image exist without creating a schema. +## Repository map -## Evaluation +```text +apps/web React browser interface +apps/api FastAPI routes, SQLAlchemy, Alembic, operations +packages/isolation_engine authoritative state, baselines, game, replay, scoring +packages/agent_runner Docker coordinator, worker, queue compatibility +tests focused backend and API flow tests +docs architecture, API, security, limits, learning map +``` -Your project will be reviewed by a Udacity reviewer against the project rubric [here](https://review.udacity.com/#!/rubrics/1801/view). Review this rubric thoroughly, and self-evaluate your project before submission. All criteria found in the rubric must meet specifications for you to pass. +## Testing + +```powershell +.\.venv\Scripts\ruff.exe format --check apps packages scripts tests +.\.venv\Scripts\ruff.exe check apps packages scripts tests +.\.venv\Scripts\pytest.exe +npm.cmd run lint --prefix apps\web +npm.cmd run typecheck --prefix apps\web +npm.cmd test --prefix apps\web -- --run +npm.cmd run build --prefix apps\web +.\.venv\Scripts\python.exe scripts\startup_check.py +``` +The main API acceptance path is `tests/integration/test_api.py::test_submit_practice_compete_leaderboard_and_replay`. It uses a controller test double so it can run without Docker. A real submitted-source run still requires the built Docker image. -## Submission +## Security scope -Before you can submit your project for review in the classroom, you must run the remote test suite & generate a zip archive of the required project files. Submit the archive in your classroom for review. (See notes on submissions below for more details.) From your terminal, run the command: (make sure to activate the aind conda environment if you're running the project in your local environment; workspace users do **not** need to activate an environment.) -``` -$ udacity submit -``` -The script will automatically create a zip archive of the required files (`my_custom_player.py` and `report.pdf` are required; `data.pickle` will be included if it exists) and submit your code to a remote server for testing. You can only submit a zip archive created by the PA script (even if you're only submitting a partial solution), and you **must submit the exact zip file created by the Project Assistant** in your classroom for review. The classroom verifies the zip file submitted against records on the Project Assistant system; any changes in the file will cause your submission to be rejected. +This is a local beta, not a production multi-tenant sandbox. Do not expose it to arbitrary internet users. Docker reduces risk but does not eliminate kernel, daemon, denial-of-service, side-channel, or supply-chain risks. -**NOTE:** Students who authenticate with Facebook or Google accounts _must_ follow the instructions on the FAQ page [here](https://project-assistant.udacity.com/faq) to obtain an authentication token. (The Workspace already includes instructions for obtaining and configuring your token.) +The original engine encoding details remain available in +[the Isolation library guide](isolation/README.md). diff --git a/Projects/3_Adversarial Search/agent_arena_lab_v7_simple.html b/Projects/3_Adversarial Search/agent_arena_lab_v7_simple.html new file mode 100644 index 00000000..b15d7f50 --- /dev/null +++ b/Projects/3_Adversarial Search/agent_arena_lab_v7_simple.html @@ -0,0 +1,871 @@ + + + + + + Agent Arena · Knight Isolation Concept + + + +
+ + +
+
+
Knight Isolation / Overview
+
+ Permanent competition + +
+
+ +
+
+
+
+
One game · one permanent points table
+

Submit once. Your Knight Isolation agent keeps competing.

+

Write a Python agent, test it against a baseline, deploy an immutable version, and follow its fixtures on a simple permanent leaderboard.

+
+ + +
+
+
+
Competition at a glance
+
+
64Active agent versions
+
1,248Completed fixtures
+
150 msFixed move budget
+
+
+
Game AAgent 1 starts
+
+
Game BAgent 2 starts
+
+
Simple points: a fixture win earns 3 points, a 1–1 mirrored draw earns 1 point per agent, and a loss earns 0.
+
+
+ +

How it works

The smallest coherent version of the arena.

+
+
1Write

Implement get_action(state) in the browser Code Lab.

+
2Test

Run conceptual scrimmages against a stable baseline and inspect logs.

+
3Submit

Create an immutable Python build under fixed platform limits.

+
4Compete

The platform assigns mirrored fixtures and updates simple points.

+
+ +

Featured fixture

A public replay from the permanent competition.

+
+
+
+
Atlas-AB v17 vs MobilityLab v12Fixture KI-01248 · Game A of 2
+ Official +
+
+
+
+
+
Atlas-AB v17@ali-lab
+
VS
+
MobilityLab v12@nova-byte
+
+
+
Fixture result: Atlas-AB wins both mirrored games, earns 3 points; MobilityLab earns 0.
+
+
+ +

Top of the table

All agents shown have completed the same number of fixtures.

+
+ + + +
#AgentDeveloperFixturesW-D-LPoints
+
+
+ +
+
+

Code & Play

A compact game viewer, problem statement, Python editor, and console inspired by browser coding arenas.

+ Simulation only · code is not executed +
+ +
+
+
+
+
YourAgent vs GreedyFoxPractice fixture · Game A · seed demo-17
+ Ready +
+
+
+ + + + + + Move 0 / 16 +
+
+ +
+
+ + + +
+
+

Block your opponent before they block you.

+

Each agent controls one knight. Every square a knight leaves becomes blocked forever. The first agent with no legal knight move loses.

+
Adversarial searchPerfect information150 ms / move
+
+
+
    +
  1. The board is 11 × 9.
  2. +
  3. Opening placements may use any open square.
  4. +
  5. Later actions use chess-knight movement.
  6. +
  7. Visited squares become permanently blocked.
  8. +
  9. A timeout, crash, or illegal action loses the game.
  10. +
  11. A ranked fixture contains two games with starting order swapped.
  12. +
+
+
+

get_action(state) must return one legal action before the fixed deadline.

+

The conceptual state exposes:

+
  • state.actions()
  • state.result(action)
  • state.player()
  • state.liberties(location)
+
+
+
+ +
+
+
+
+ + agent.py +
+
+ + +
+
+
+ + +
+
+ +
+
+
+ +
+ Idle +
+
+
+ + + +
+
+
+
+
+ +
+
+

Permanent Knight Isolation leaderboard

No rating model: only equal-fixture totals using 3 points for a win, 1 for a draw, and 0 for a loss.

+ 42 fixtures per active version +
+
Fixture definition: agents play two mirrored games. A 2–0 result is a win; 1–1 is a draw; 0–2 is a loss.
+
+
+ + + +
#Agent versionDeveloperFixturesWinsDrawsLossesPointsForm
+
+
+
+
Selected competitor

Atlas-AB v17

@ali-lab
+ Active +
+
AGT-KI-00017 · AGV-KI-00017-17 · build 8fa31c2
+
+
88Points
+
28Wins
+
4Draws
+
10Losses
+
+
Strong mobility heuristic with stable time management.
+
+
Recent fixtures
+
+
+
+
+
+ +
+
+

My Agents

Manage your Knight Isolation lineages and immutable versions.

+ +
+
Only one version per lineage is active in the permanent points table. Older versions remain privately visible and auditable.
+
+
+ +
+
+

Submission Lab

A condensed conceptual flow for creating a new agent or immutable version.

+ No real upload or deployment +
+
+
+
+ + +
+
+
+
+ +
Knight Isolation · locked
+
Python 3.12 · pinned
+
150 ms · platform controlled
+
Code Lab buffer · unsaved draft
+
+
+
+ + +
+
+
+
Conceptual pipeline
+

What the finished platform would do

+
+
1
PackageSnapshot source and assign stable IDs.
Waiting
+
2
Check safetyConceptual screening before isolated execution.
Waiting
+
3
ValidateImport, legal actions, deterministic replay and timeouts.
Waiting
+
4
Deploy versionImmutable private build enters the points competition.
Waiting
+
+

Fixed platform policies

+
No network · read-only root · bounded scratch space · CPU, wall-time, memory and output limits · private source by default · auditable replay artifacts.
+
+
+
+
+
+
+ + + +
+ + + + diff --git a/Projects/3_Adversarial Search/alembic.ini b/Projects/3_Adversarial Search/alembic.ini new file mode 100644 index 00000000..38ab1ff6 --- /dev/null +++ b/Projects/3_Adversarial Search/alembic.ini @@ -0,0 +1,31 @@ +[alembic] +script_location = apps/api/alembic +prepend_sys_path = . apps/api packages/isolation_engine packages/agent_runner +sqlalchemy.url = sqlite:///var/agent_arena.db + +[loggers] +keys = root,sqlalchemy,alembic +[handlers] +keys = console +[formatters] +keys = generic +[logger_root] +level = WARN +handlers = console +qualname = +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine +[logger_alembic] +level = INFO +handlers = +qualname = alembic +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/Projects/3_Adversarial Search/apps/api/agent_arena_api/__init__.py b/Projects/3_Adversarial Search/apps/api/agent_arena_api/__init__.py new file mode 100644 index 00000000..c8b77ede --- /dev/null +++ b/Projects/3_Adversarial Search/apps/api/agent_arena_api/__init__.py @@ -0,0 +1 @@ +"""Agent Arena FastAPI application package.""" diff --git a/Projects/3_Adversarial Search/apps/api/agent_arena_api/api.py b/Projects/3_Adversarial Search/apps/api/agent_arena_api/api.py new file mode 100644 index 00000000..b61d9d00 --- /dev/null +++ b/Projects/3_Adversarial Search/apps/api/agent_arena_api/api.py @@ -0,0 +1,255 @@ +from __future__ import annotations + +from typing import Annotated + +from agent_runner import DockerAgent +from fastapi import APIRouter, Depends, HTTPException, Query, Request, status +from isolation_engine import ENGINE_VERSION, RULESET_VERSION +from sqlalchemy import select +from sqlalchemy.exc import OperationalError +from sqlalchemy.orm import Session + +from .competition import agent_fixture_history, build_leaderboard, fixture_details, run_round +from .database import get_session +from .models import Agent, Fixture, Submission +from .schemas import ( + AgentCreate, + AgentFixtureHistoryEntry, + AgentResponse, + FixtureResponse, + HealthResponse, + LeaderboardEntry, + PracticeCreate, + PracticeResponse, + RecordResponse, + RoundResponse, + RulesResponse, + SubmissionCreate, + SubmissionResponse, +) +from .submissions import prepare_submission, run_practice, save_new_agent, save_submission + +router = APIRouter(prefix="/api") +SessionDependency = Annotated[Session, Depends(get_session)] + + +@router.get("/health", response_model=HealthResponse) +def health(request: Request) -> HealthResponse: + settings = request.app.state.settings + database_status = "ready" + try: + with request.app.state.database.sessions() as session: + session.scalar(select(Agent.id).limit(1)) + except OperationalError: + database_status = "migration_required" + return HealthResponse( + database=database_status, + runner=DockerAgent.diagnostic(settings.runner_image), + ) + + +@router.get("/rules", response_model=RulesResponse) +def rules(request: Request) -> RulesResponse: + settings = request.app.state.settings + return RulesResponse( + move_budget_ms=settings.move_budget_ms, + minimum_ranked_fixtures=settings.minimum_ranked_fixtures, + engine_version=ENGINE_VERSION, + ruleset_version=RULESET_VERSION, + demo_owner_name=settings.demo_owner_name, + ) + + +@router.get("/leaderboard", response_model=list[LeaderboardEntry]) +def leaderboard(request: Request, session: SessionDependency) -> list[LeaderboardEntry]: + rows = build_leaderboard(session, request.app.state.settings.minimum_ranked_fixtures) + return [LeaderboardEntry.model_validate(row) for row in rows] + + +@router.get("/agents", response_model=list[AgentResponse]) +def agents( + session: SessionDependency, + owner_name: Annotated[str | None, Query(max_length=80)] = None, +) -> list[AgentResponse]: + statement = select(Agent).order_by(Agent.owner_name, Agent.name) + if owner_name: + statement = statement.where(Agent.owner_name == owner_name) + return [_agent_response(session, item) for item in session.scalars(statement)] + + +@router.get("/agents/{agent_id}", response_model=AgentResponse) +def agent(agent_id: str, session: SessionDependency) -> AgentResponse: + item = session.get(Agent, agent_id) + if item is None: + raise HTTPException(status_code=404, detail="agent was not found") + return _agent_response(session, item) + + +@router.get( + "/agents/{agent_id}/fixtures", + response_model=list[AgentFixtureHistoryEntry], +) +def agent_fixtures( + agent_id: str, + session: SessionDependency, +) -> list[AgentFixtureHistoryEntry]: + if session.get(Agent, agent_id) is None: + raise HTTPException(status_code=404, detail="agent was not found") + return [ + AgentFixtureHistoryEntry.model_validate(item) + for item in agent_fixture_history(session, agent_id) + ] + + +@router.post("/agents", response_model=AgentResponse, status_code=status.HTTP_201_CREATED) +def create_agent( + payload: AgentCreate, + request: Request, + session: SessionDependency, +) -> AgentResponse: + settings = request.app.state.settings + try: + prepared = prepare_submission(payload.source, settings) + except ValueError as error: + raise HTTPException(status_code=400, detail=str(error)) from error + if not prepared.accepted: + raise HTTPException(status_code=422, detail=prepared.validation) + try: + item = save_new_agent( + session, + settings, + owner_name=payload.owner_name, + name=payload.name, + prepared=prepared, + ) + session.commit() + session.refresh(item) + return _agent_response(session, item) + except ValueError as error: + session.rollback() + raise HTTPException(status_code=409, detail=str(error)) from error + + +@router.post( + "/agents/{agent_id}/submissions", + response_model=AgentResponse, + status_code=status.HTTP_201_CREATED, +) +def create_submission( + agent_id: str, + payload: SubmissionCreate, + request: Request, + session: SessionDependency, +) -> AgentResponse: + item = session.get(Agent, agent_id) + if item is None: + raise HTTPException(status_code=404, detail="agent was not found") + settings = request.app.state.settings + try: + prepared = prepare_submission(payload.source, settings) + except ValueError as error: + raise HTTPException(status_code=400, detail=str(error)) from error + if not prepared.accepted: + raise HTTPException(status_code=422, detail=prepared.validation) + save_submission(session, settings, agent=item, prepared=prepared) + session.commit() + session.refresh(item) + return _agent_response(session, item) + + +@router.post("/practice", response_model=PracticeResponse) +def practice(payload: PracticeCreate, request: Request) -> PracticeResponse: + try: + result = run_practice( + payload.source, + payload.baseline, + payload.seed, + request.app.state.settings, + ) + except ValueError as error: + raise HTTPException(status_code=400, detail=str(error)) from error + return PracticeResponse.model_validate(result) + + +@router.get("/fixtures/featured", response_model=FixtureResponse | None) +def featured_fixture(session: SessionDependency) -> FixtureResponse | None: + item = session.scalar( + select(Fixture) + .where(Fixture.status == "complete") + .order_by(Fixture.completed_at.desc(), Fixture.id) + .limit(1) + ) + return FixtureResponse.model_validate(fixture_details(session, item)) if item else None + + +@router.get("/fixtures/{fixture_id}", response_model=FixtureResponse) +def fixture(fixture_id: str, session: SessionDependency) -> FixtureResponse: + item = session.get(Fixture, fixture_id) + if item is None: + raise HTTPException(status_code=404, detail="fixture was not found") + return FixtureResponse.model_validate(fixture_details(session, item)) + + +@router.post("/competition/run-round", response_model=RoundResponse) +def run_competition_round(request: Request) -> RoundResponse: + database = request.app.state.database + try: + fixture_ids = run_round(database, request.app.state.settings) + except ValueError as error: + raise HTTPException(status_code=409, detail=str(error)) from error + return RoundResponse( + fixture_ids=fixture_ids, + participant_count=_participant_count(len(fixture_ids)), + ) + + +def _agent_response(session: Session, item: Agent) -> AgentResponse: + submissions = list( + session.scalars( + select(Submission) + .where(Submission.agent_id == item.id) + .order_by(Submission.number.desc()) + ) + ) + table = build_leaderboard(session) + active_record = next( + (row for row in table if row["submission_id"] == item.active_submission_id), + None, + ) + record = None + if active_record: + record = RecordResponse( + fixtures=active_record["fixtures"], + wins=active_record["wins"], + draws=active_record["draws"], + losses=active_record["losses"], + points=active_record["points"], + ) + return AgentResponse( + id=item.id, + owner_name=item.owner_name, + name=item.name, + active_submission_id=item.active_submission_id, + created_at=item.created_at, + submissions=[ + SubmissionResponse( + id=submission.id, + agent_id=submission.agent_id, + number=submission.number, + source_hash=submission.source_hash, + execution_kind=submission.execution_kind, + validation_result=submission.validation_result, + created_at=submission.created_at, + is_active=submission.id == item.active_submission_id, + ) + for submission in submissions + ], + record=record, + ) + + +def _participant_count(fixture_count: int) -> int: + count = 0 + while count * (count - 1) // 2 < fixture_count: + count += 1 + return count diff --git a/Projects/3_Adversarial Search/apps/api/agent_arena_api/benchmark_seed.py b/Projects/3_Adversarial Search/apps/api/agent_arena_api/benchmark_seed.py new file mode 100644 index 00000000..10e004c4 --- /dev/null +++ b/Projects/3_Adversarial Search/apps/api/agent_arena_api/benchmark_seed.py @@ -0,0 +1,73 @@ +"""Seed only the isolated benchmark database with trusted comparison agents.""" + +from __future__ import annotations + +import hashlib +import json +import uuid +from dataclasses import asdict + +from isolation_engine import BENCHMARK_STRATEGY_LIST +from sqlalchemy import func, select + +from .config import settings +from .database import Database +from .models import Agent, Fixture, Submission + +BENCHMARK_OWNER = "benchmark-lab" +BENCHMARK_NAMESPACE = uuid.UUID("f0e2dbd3-9718-46ed-9890-f9dc4329886c") + + +def benchmark_id(kind: str, strategy_key: str) -> str: + return str(uuid.uuid5(BENCHMARK_NAMESPACE, f"{kind}:{strategy_key}")) + + +def seed_benchmark_database(database: Database) -> dict[str, int]: + with database.sessions.begin() as session: + for strategy in BENCHMARK_STRATEGY_LIST: + agent_id = benchmark_id("agent", strategy.key) + submission_id = benchmark_id("submission", strategy.key) + if session.get(Agent, agent_id) is not None: + continue + agent = Agent( + id=agent_id, + owner_name=BENCHMARK_OWNER, + name=strategy.agent_name, + active_submission_id=submission_id, + ) + submission = Submission( + id=submission_id, + agent_id=agent_id, + number=1, + source_hash=hashlib.sha256( + f"trusted-benchmark:{strategy.key}:1".encode() + ).hexdigest(), + source_path=f"builtin://benchmark/{strategy.key}", + execution_kind=strategy.key, + validation_result={ + "accepted": True, + "trusted_benchmark": True, + "strategy": asdict(strategy), + }, + ) + session.add_all([agent, submission]) + + with database.sessions() as session: + return { + "agents": session.scalar(select(func.count(Agent.id))) or 0, + "submissions": session.scalar(select(func.count(Submission.id))) or 0, + "fixtures": session.scalar(select(func.count(Fixture.id))) or 0, + } + + +def main() -> int: + database = Database.create(settings.database_url) + try: + print(json.dumps(seed_benchmark_database(database), indent=2)) + finally: + database.engine.dispose() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Projects/3_Adversarial Search/apps/api/agent_arena_api/cli.py b/Projects/3_Adversarial Search/apps/api/agent_arena_api/cli.py new file mode 100644 index 00000000..3c879292 --- /dev/null +++ b/Projects/3_Adversarial Search/apps/api/agent_arena_api/cli.py @@ -0,0 +1,77 @@ +from __future__ import annotations + +import argparse +import json +import sqlite3 +from pathlib import Path + +from agent_runner import DockerAgent + +from .competition import run_round +from .config import settings +from .database import Database +from .seed import seed_database + + +def main() -> int: + parser = argparse.ArgumentParser(prog="agent-arena") + parser.add_argument("command", choices=("seed", "run-round", "doctor")) + args = parser.parse_args() + + if args.command == "doctor": + print( + json.dumps( + { + "database": _database_diagnostic(), + "runner": DockerAgent.diagnostic(settings.runner_image), + }, + indent=2, + ) + ) + return 0 + + database = Database.create(settings.database_url) + try: + if args.command == "seed": + print(json.dumps(seed_database(database, settings), indent=2)) + elif args.command == "run-round": + fixture_ids = run_round(database, settings) + print(json.dumps({"fixtures": fixture_ids, "status": "complete"}, indent=2)) + finally: + database.engine.dispose() + return 0 + + +def _database_diagnostic() -> dict[str, object]: + prefix = "sqlite:///" + if not settings.database_url.startswith(prefix): + return {"available": True, "url": settings.database_url} + path = Path(settings.database_url.removeprefix(prefix)) + if not path.exists(): + return { + "available": False, + "path": str(path), + "message": "Run the Alembic migration first.", + } + expected = {"alembic_version", "agents", "submissions", "fixtures", "games"} + try: + with sqlite3.connect(f"file:{path.as_posix()}?mode=ro", uri=True) as connection: + rows = connection.execute("SELECT name FROM sqlite_master WHERE type='table'") + tables = {row[0] for row in rows} + except sqlite3.Error as error: + return {"available": False, "path": str(path), "message": str(error)} + if not expected.issubset(tables): + return { + "available": False, + "path": str(path), + "message": "The earlier beta schema is present; back it up and run the new migration.", + } + return { + "available": True, + "path": str(path), + "message": "Simplified database schema is ready.", + } + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Projects/3_Adversarial Search/apps/api/agent_arena_api/competition.py b/Projects/3_Adversarial Search/apps/api/agent_arena_api/competition.py new file mode 100644 index 00000000..b26bb2e1 --- /dev/null +++ b/Projects/3_Adversarial Search/apps/api/agent_arena_api/competition.py @@ -0,0 +1,339 @@ +"""Mirrored fixtures, persisted games, and leaderboard calculation.""" + +from __future__ import annotations + +from itertools import combinations +from pathlib import Path +from typing import Any + +from agent_runner import DockerAgent +from isolation_engine import ( + BENCHMARK_STRATEGIES, + BaselineController, + BenchmarkController, + GameResult, + play_game, + score_fixture, +) +from sqlalchemy import or_, select +from sqlalchemy.orm import Session + +from .config import Settings +from .database import Database +from .models import Agent, Fixture, Game, Submission, utcnow + + +def run_round(database: Database, settings: Settings) -> list[str]: + """Synchronously play one fixture for every pair of active submissions.""" + + with database.sessions() as session: + agents = list( + session.scalars( + select(Agent).where(Agent.active_submission_id.is_not(None)).order_by(Agent.name) + ) + ) + submissions = [session.get(Submission, agent.active_submission_id) for agent in agents] + active = [submission for submission in submissions if submission is not None] + if len(active) < 2: + raise ValueError("at least two active submissions are required") + + fixture_ids: list[str] = [] + for submission_a, submission_b in combinations(active, 2): + with database.sessions.begin() as session: + fixture = Fixture( + submission_a_id=submission_a.id, + submission_b_id=submission_b.id, + status="running", + ) + session.add(fixture) + session.flush() + fixture_ids.append(fixture.id) + _play_fixture(database, settings, fixture.id, submission_a, submission_b) + return fixture_ids + + +def _play_fixture( + database: Database, + settings: Settings, + fixture_id: str, + submission_a: Submission, + submission_b: Submission, +) -> None: + try: + game_a = play_game( + controller_for(submission_a, settings, seed=101), + controller_for(submission_b, settings, seed=202), + time_limit_ms=settings.move_budget_ms, + ) + game_b = play_game( + controller_for(submission_b, settings, seed=303), + controller_for(submission_a, settings, seed=404), + time_limit_ms=settings.move_budget_ms, + ) + with database.sessions.begin() as session: + fixture = session.get(Fixture, fixture_id) + assert fixture is not None + session.add_all( + [ + game_model(fixture.id, 1, submission_a.id, submission_b.id, game_a), + game_model(fixture.id, 2, submission_b.id, submission_a.id, game_b), + ] + ) + fixture.status = "complete" + fixture.completed_at = utcnow() + except Exception: + with database.sessions.begin() as session: + fixture = session.get(Fixture, fixture_id) + assert fixture is not None + fixture.status = "failed" + fixture.completed_at = utcnow() + raise + + +def controller_for(submission: Submission, settings: Settings, seed: int) -> Any: + if submission.execution_kind in {"random", "greedy", "minimax"}: + return BaselineController(submission.execution_kind, seed=seed) + if submission.execution_kind in BENCHMARK_STRATEGIES: + return BenchmarkController(submission.execution_kind, seed=seed) + return DockerAgent(Path(submission.source_path), settings.runner_image) + + +def game_model( + fixture_id: str, + leg_number: int, + first_id: str, + second_id: str, + result: GameResult, +) -> Game: + winner_id = first_id if result.winner_player == 0 else second_id + return Game( + fixture_id=fixture_id, + leg_number=leg_number, + first_submission_id=first_id, + second_submission_id=second_id, + winner_submission_id=winner_id, + end_status=result.end_status, + losing_reason=result.losing_reason, + replay=result.replay_dicts(), + engine_version=result.engine_version, + ruleset_version=result.ruleset_version, + ) + + +def fixture_score(session: Session, fixture: Fixture) -> Any | None: + games = list( + session.scalars(select(Game).where(Game.fixture_id == fixture.id).order_by(Game.leg_number)) + ) + if len(games) != 2 or any(game.winner_submission_id is None for game in games): + return None + winners = tuple( + 0 if game.winner_submission_id == fixture.submission_a_id else 1 for game in games + ) + return score_fixture((winners[0], winners[1])) + + +def build_leaderboard( + session: Session, + minimum_ranked_fixtures: int = 10, +) -> list[dict[str, Any]]: + """Calculate points directly from completed fixtures at read time.""" + + agents = list(session.scalars(select(Agent).order_by(Agent.name))) + active_agents = [agent for agent in agents if agent.active_submission_id] + entries: dict[str, dict[str, Any]] = {} + for agent in active_agents: + submission = session.get(Submission, agent.active_submission_id) + if submission is None: + continue + entries[submission.id] = { + "agent_id": agent.id, + "agent_name": agent.name, + "owner_name": agent.owner_name, + "submission_id": submission.id, + "submission_number": submission.number, + "fixtures": 0, + "wins": 0, + "draws": 0, + "losses": 0, + "points": 0, + "recent_fixtures": [], + } + + all_submissions = {item.id: item for item in session.scalars(select(Submission))} + all_agents = {item.id: item for item in agents} + fixtures = list( + session.scalars( + select(Fixture) + .where(Fixture.status == "complete") + .order_by(Fixture.completed_at.desc(), Fixture.id) + ) + ) + for fixture in fixtures: + scored = fixture_score(session, fixture) + if scored is None or fixture.completed_at is None: + continue + for submission_id, points, opponent_id in ( + (fixture.submission_a_id, scored.points_a, fixture.submission_b_id), + (fixture.submission_b_id, scored.points_b, fixture.submission_a_id), + ): + entry = entries.get(submission_id) + if entry is None: + continue + entry["fixtures"] += 1 + entry["points"] += points + if scored.is_draw: + entry["draws"] += 1 + outcome = "D" + elif points == 3: + entry["wins"] += 1 + outcome = "W" + else: + entry["losses"] += 1 + outcome = "L" + opponent_submission = all_submissions[opponent_id] + opponent = all_agents[opponent_submission.agent_id] + opponent_points = ( + scored.points_b if submission_id == fixture.submission_a_id else scored.points_a + ) + entry["recent_fixtures"].append( + { + "id": fixture.id, + "opponent_name": opponent.name, + "outcome": outcome, + "score": f"{points}-{opponent_points}", + "completed_at": fixture.completed_at, + } + ) + + for entry in entries.values(): + fixtures_played = entry["fixtures"] + entry["points_per_fixture"] = entry["points"] / fixtures_played if fixtures_played else 0.0 + entry["qualified"] = fixtures_played >= minimum_ranked_fixtures + + ordered = sorted( + entries.values(), + key=lambda item: ( + not item["qualified"], + -item["points_per_fixture"], + -item["points"], + -item["wins"], + item["agent_name"].lower(), + ), + ) + rank = 0 + for entry in ordered: + if entry["qualified"]: + rank += 1 + entry["rank"] = rank + else: + entry["rank"] = None + entry["recent_fixtures"] = entry["recent_fixtures"][:5] + entry["recent_form"] = [item["outcome"] for item in entry["recent_fixtures"]] + del entry["recent_fixtures"] + return ordered + + +def agent_fixture_history(session: Session, agent_id: str) -> list[dict[str, Any]]: + submissions = list( + session.scalars( + select(Submission).where(Submission.agent_id == agent_id).order_by(Submission.number) + ) + ) + submission_by_id = {submission.id: submission for submission in submissions} + if not submission_by_id: + return [] + submission_ids = tuple(submission_by_id) + fixtures = list( + session.scalars( + select(Fixture) + .where( + Fixture.status == "complete", + or_( + Fixture.submission_a_id.in_(submission_ids), + Fixture.submission_b_id.in_(submission_ids), + ), + ) + .order_by(Fixture.completed_at.desc(), Fixture.id) + ) + ) + history: list[dict[str, Any]] = [] + for fixture in fixtures: + scored = fixture_score(session, fixture) + if scored is None or fixture.completed_at is None: + continue + agent_is_a = fixture.submission_a_id in submission_by_id + own_id = fixture.submission_a_id if agent_is_a else fixture.submission_b_id + opponent_id = fixture.submission_b_id if agent_is_a else fixture.submission_a_id + own_points = scored.points_a if agent_is_a else scored.points_b + opponent_points = scored.points_b if agent_is_a else scored.points_a + outcome = "D" if scored.is_draw else "W" if own_points == 3 else "L" + opponent_submission = session.get(Submission, opponent_id) + assert opponent_submission is not None + opponent = session.get(Agent, opponent_submission.agent_id) + assert opponent is not None + history.append( + { + "id": fixture.id, + "submission_number": submission_by_id[own_id].number, + "opponent_name": opponent.name, + "opponent_submission_number": opponent_submission.number, + "outcome": outcome, + "score": f"{own_points}-{opponent_points}", + "completed_at": fixture.completed_at, + } + ) + return history + + +def fixture_details(session: Session, fixture: Fixture) -> dict[str, Any]: + submission_a = session.get(Submission, fixture.submission_a_id) + submission_b = session.get(Submission, fixture.submission_b_id) + assert submission_a is not None and submission_b is not None + agent_a = session.get(Agent, submission_a.agent_id) + agent_b = session.get(Agent, submission_b.agent_id) + assert agent_a is not None and agent_b is not None + games = list( + session.scalars(select(Game).where(Game.fixture_id == fixture.id).order_by(Game.leg_number)) + ) + scored = fixture_score(session, fixture) + winner_submission_id = None + is_draw = False + points_a = points_b = 0 + if scored is not None: + points_a, points_b = scored.points_a, scored.points_b + is_draw = scored.is_draw + if scored.winner == 0: + winner_submission_id = fixture.submission_a_id + elif scored.winner == 1: + winner_submission_id = fixture.submission_b_id + return { + "id": fixture.id, + "submission_a_id": fixture.submission_a_id, + "submission_b_id": fixture.submission_b_id, + "agent_a_name": agent_a.name, + "agent_b_name": agent_b.name, + "status": fixture.status, + "created_at": fixture.created_at, + "completed_at": fixture.completed_at, + "winner_submission_id": winner_submission_id, + "is_draw": is_draw, + "points_a": points_a, + "points_b": points_b, + "games": [ + { + "id": game.id, + "leg_number": game.leg_number, + "first_submission_id": game.first_submission_id, + "second_submission_id": game.second_submission_id, + "winner_submission_id": game.winner_submission_id, + "end_status": game.end_status, + "losing_reason": game.losing_reason, + "replay": game.replay, + "engine_version": game.engine_version, + "ruleset_version": game.ruleset_version, + "started_at": game.started_at, + "completed_at": game.completed_at, + } + for game in games + ], + } diff --git a/Projects/3_Adversarial Search/apps/api/agent_arena_api/config.py b/Projects/3_Adversarial Search/apps/api/agent_arena_api/config.py new file mode 100644 index 00000000..8c586f04 --- /dev/null +++ b/Projects/3_Adversarial Search/apps/api/agent_arena_api/config.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from pathlib import Path + +from pydantic_settings import BaseSettings, SettingsConfigDict + +ROOT = Path(__file__).resolve().parents[3] + + +class Settings(BaseSettings): + model_config = SettingsConfigDict( + env_file=ROOT / ".env", env_prefix="AGENT_ARENA_", extra="ignore" + ) + + database_url: str = f"sqlite:///{(ROOT / 'var' / 'agent_arena.db').as_posix()}" + source_root: Path = ROOT / "var" / "sources" + temp_root: Path = ROOT / "var" / "tmp" + runner_image: str = "agent-arena-runner:py312-v1" + move_budget_ms: int = 150 + minimum_ranked_fixtures: int = 10 + demo_owner_name: str = "oualid" + max_source_bytes: int = 131_072 + + +settings = Settings() diff --git a/Projects/3_Adversarial Search/apps/api/agent_arena_api/database.py b/Projects/3_Adversarial Search/apps/api/agent_arena_api/database.py new file mode 100644 index 00000000..6272a39f --- /dev/null +++ b/Projects/3_Adversarial Search/apps/api/agent_arena_api/database.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from collections.abc import Generator +from dataclasses import dataclass + +from fastapi import Request +from sqlalchemy import Engine, create_engine, event +from sqlalchemy.orm import Session, sessionmaker + + +@dataclass +class Database: + engine: Engine + sessions: sessionmaker[Session] + + @classmethod + def create(cls, url: str) -> Database: + connect_args = {"check_same_thread": False} if url.startswith("sqlite") else {} + engine = create_engine(url, connect_args=connect_args) + if url.startswith("sqlite"): + event.listen(engine, "connect", _enable_sqlite_foreign_keys) + return cls(engine=engine, sessions=sessionmaker(engine, expire_on_commit=False)) + + +def _enable_sqlite_foreign_keys(connection: object, _: object) -> None: + cursor = connection.cursor() # type: ignore[attr-defined] + cursor.execute("PRAGMA foreign_keys=ON") + cursor.close() + + +def get_session(request: Request) -> Generator[Session, None, None]: + with request.app.state.database.sessions() as session: + yield session diff --git a/Projects/3_Adversarial Search/apps/api/agent_arena_api/main.py b/Projects/3_Adversarial Search/apps/api/agent_arena_api/main.py new file mode 100644 index 00000000..928365bf --- /dev/null +++ b/Projects/3_Adversarial Search/apps/api/agent_arena_api/main.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from contextlib import asynccontextmanager + +from fastapi import FastAPI + +from .api import router +from .config import Settings +from .config import settings as default_settings +from .database import Database + + +def create_app(settings: Settings | None = None) -> FastAPI: + active_settings = settings or default_settings + database = Database.create(active_settings.database_url) + + @asynccontextmanager + async def lifespan(_: FastAPI): + active_settings.source_root.mkdir(parents=True, exist_ok=True) + active_settings.temp_root.mkdir(parents=True, exist_ok=True) + yield + database.engine.dispose() + + app = FastAPI( + title="Agent Arena local beta API", + version="0.2.0", + description=( + "Local Knight Isolation API. Submitted source is imported only inside " + "the Docker runner; this is not a production sandbox." + ), + lifespan=lifespan, + ) + app.state.database = database + app.state.settings = active_settings + app.include_router(router) + return app + + +app = create_app() diff --git a/Projects/3_Adversarial Search/apps/api/agent_arena_api/models.py b/Projects/3_Adversarial Search/apps/api/agent_arena_api/models.py new file mode 100644 index 00000000..4f1287a1 --- /dev/null +++ b/Projects/3_Adversarial Search/apps/api/agent_arena_api/models.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import uuid +from datetime import UTC, datetime +from typing import Any + +from sqlalchemy import JSON, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column + + +def new_id() -> str: + return str(uuid.uuid4()) + + +def utcnow() -> datetime: + return datetime.now(UTC) + + +class Base(DeclarativeBase): + pass + + +class Agent(Base): + """Stable public identity for one competitor.""" + + __tablename__ = "agents" + __table_args__ = (UniqueConstraint("owner_name", "name", name="uq_owner_agent_name"),) + + id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id) + owner_name: Mapped[str] = mapped_column(String(80), index=True) + name: Mapped[str] = mapped_column(String(100)) + active_submission_id: Mapped[str | None] = mapped_column(String(36), nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) + + +class Submission(Base): + """Immutable source used by historical games.""" + + __tablename__ = "submissions" + __table_args__ = (UniqueConstraint("agent_id", "number", name="uq_agent_submission"),) + + id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id) + agent_id: Mapped[str] = mapped_column(ForeignKey("agents.id"), index=True) + number: Mapped[int] = mapped_column(Integer) + source_hash: Mapped[str] = mapped_column(String(64)) + source_path: Mapped[str] = mapped_column(Text) + execution_kind: Mapped[str] = mapped_column(String(20), default="docker") + validation_result: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict) + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) + + +class Fixture(Base): + """Two mirrored games between the same submissions.""" + + __tablename__ = "fixtures" + + id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id) + submission_a_id: Mapped[str] = mapped_column(ForeignKey("submissions.id"), index=True) + submission_b_id: Mapped[str] = mapped_column(ForeignKey("submissions.id"), index=True) + status: Mapped[str] = mapped_column(String(20), default="running", index=True) + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) + completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) + + +class Game(Base): + """One leg and its single canonical replay.""" + + __tablename__ = "games" + __table_args__ = (UniqueConstraint("fixture_id", "leg_number", name="uq_fixture_leg"),) + + id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id) + fixture_id: Mapped[str] = mapped_column(ForeignKey("fixtures.id"), index=True) + leg_number: Mapped[int] = mapped_column(Integer) + first_submission_id: Mapped[str] = mapped_column(ForeignKey("submissions.id")) + second_submission_id: Mapped[str] = mapped_column(ForeignKey("submissions.id")) + winner_submission_id: Mapped[str | None] = mapped_column(String(36), nullable=True) + end_status: Mapped[str] = mapped_column(String(30)) + losing_reason: Mapped[str] = mapped_column(Text, default="") + replay: Mapped[list[dict[str, Any]]] = mapped_column(JSON, default=list) + engine_version: Mapped[str] = mapped_column(String(80)) + ruleset_version: Mapped[str] = mapped_column(String(80)) + started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) + completed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) diff --git a/Projects/3_Adversarial Search/apps/api/agent_arena_api/schemas.py b/Projects/3_Adversarial Search/apps/api/agent_arena_api/schemas.py new file mode 100644 index 00000000..523ac576 --- /dev/null +++ b/Projects/3_Adversarial Search/apps/api/agent_arena_api/schemas.py @@ -0,0 +1,142 @@ +from __future__ import annotations + +from datetime import datetime +from typing import Any, Literal + +from pydantic import BaseModel, Field + + +class HealthResponse(BaseModel): + status: Literal["ok"] = "ok" + database: Literal["ready", "migration_required"] + runner: dict[str, Any] + + +class RulesResponse(BaseModel): + game: Literal["knight_isolation"] = "knight_isolation" + board_width: int = 11 + board_height: int = 9 + runtime: str = "Python 3.12" + move_budget_ms: int = 150 + minimum_ranked_fixtures: int = 10 + fixture_games: int = 2 + points: dict[str, int] = {"win": 3, "draw": 1, "loss": 0} + engine_version: str + ruleset_version: str + demo_owner_name: str + + +class AgentCreate(BaseModel): + owner_name: str = Field(min_length=1, max_length=80) + name: str = Field(min_length=1, max_length=100) + source: str = Field(min_length=1) + + +class SubmissionCreate(BaseModel): + source: str = Field(min_length=1) + + +class PracticeCreate(BaseModel): + source: str = Field(min_length=1) + baseline: Literal["random", "greedy", "minimax"] = "greedy" + seed: int = 17 + + +class SubmissionResponse(BaseModel): + id: str + agent_id: str + number: int + source_hash: str + execution_kind: str + validation_result: dict[str, Any] + created_at: datetime + is_active: bool + + +class RecordResponse(BaseModel): + fixtures: int + wins: int + draws: int + losses: int + points: int + + +class AgentResponse(BaseModel): + id: str + owner_name: str + name: str + active_submission_id: str | None + created_at: datetime + submissions: list[SubmissionResponse] + record: RecordResponse | None = None + + +class LeaderboardEntry(BaseModel): + rank: int | None + agent_id: str + agent_name: str + owner_name: str + submission_id: str + submission_number: int + fixtures: int + wins: int + draws: int + losses: int + points: int + points_per_fixture: float + qualified: bool + recent_form: list[Literal["W", "D", "L"]] + + +class AgentFixtureHistoryEntry(BaseModel): + id: str + submission_number: int + opponent_name: str + opponent_submission_number: int + outcome: Literal["W", "D", "L"] + score: str + completed_at: datetime + + +class PracticeResponse(BaseModel): + status: str + result: dict[str, Any] + replay: list[dict[str, Any]] + logs: list[dict[str, str]] + + +class GameResponse(BaseModel): + id: str + leg_number: int + first_submission_id: str + second_submission_id: str + winner_submission_id: str | None + end_status: str + losing_reason: str + replay: list[dict[str, Any]] + engine_version: str + ruleset_version: str + started_at: datetime + completed_at: datetime + + +class FixtureResponse(BaseModel): + id: str + submission_a_id: str + submission_b_id: str + agent_a_name: str + agent_b_name: str + status: str + created_at: datetime + completed_at: datetime | None + winner_submission_id: str | None + is_draw: bool + points_a: int + points_b: int + games: list[GameResponse] + + +class RoundResponse(BaseModel): + fixture_ids: list[str] + status: Literal["complete"] = "complete" + participant_count: int diff --git a/Projects/3_Adversarial Search/apps/api/agent_arena_api/seed.py b/Projects/3_Adversarial Search/apps/api/agent_arena_api/seed.py new file mode 100644 index 00000000..f3000854 --- /dev/null +++ b/Projects/3_Adversarial Search/apps/api/agent_arena_api/seed.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +import hashlib + +from sqlalchemy import func, select + +from .competition import run_round +from .config import Settings +from .database import Database +from .models import Agent, Fixture, Submission, utcnow + +SEED_AGENTS = ( + ( + "31111111-1111-4111-8111-111111111111", + "41111111-1111-4111-8111-111111111111", + "oualid", + "Atlas-AB", + "minimax", + ), + ( + "32222222-2222-4222-8222-222222222222", + "42222222-2222-4222-8222-222222222222", + "oualid", + "PartitionScout", + "greedy", + ), + ( + "33333333-3333-4333-8333-333333333333", + "43333333-3333-4333-8333-333333333333", + "starter-kit", + "GreedyFox", + "greedy", + ), + ( + "34444444-4444-4444-8444-444444444444", + "44444444-4444-4444-8444-444444444444", + "starter-kit", + "RandomKnight", + "random", + ), +) + + +def seed_database(database: Database, settings: Settings) -> dict[str, int]: + with database.sessions.begin() as session: + for agent_id, submission_id, owner_name, name, execution_kind in SEED_AGENTS: + if session.get(Agent, agent_id) is not None: + continue + source_hash = hashlib.sha256(f"trusted:{execution_kind}:1".encode()).hexdigest() + agent = Agent( + id=agent_id, + owner_name=owner_name, + name=name, + active_submission_id=submission_id, + ) + submission = Submission( + id=submission_id, + agent_id=agent_id, + number=1, + source_hash=source_hash, + source_path=f"builtin://{execution_kind}", + execution_kind=execution_kind, + validation_result={ + "accepted": True, + "trusted_builtin": True, + "controller": execution_kind, + }, + created_at=utcnow(), + ) + session.add_all([agent, submission]) + + with database.sessions() as session: + fixture_count = session.scalar(select(func.count(Fixture.id))) or 0 + if fixture_count == 0: + run_round(database, settings) + + with database.sessions() as session: + return { + "agents": session.scalar(select(func.count(Agent.id))) or 0, + "submissions": session.scalar(select(func.count(Submission.id))) or 0, + "fixtures": session.scalar(select(func.count(Fixture.id))) or 0, + } diff --git a/Projects/3_Adversarial Search/apps/api/agent_arena_api/submissions.py b/Projects/3_Adversarial Search/apps/api/agent_arena_api/submissions.py new file mode 100644 index 00000000..fc2dff85 --- /dev/null +++ b/Projects/3_Adversarial Search/apps/api/agent_arena_api/submissions.py @@ -0,0 +1,307 @@ +"""Straight-line source validation, storage, and practice operations.""" + +from __future__ import annotations + +import ast +import hashlib +from collections.abc import Iterator +from contextlib import contextmanager +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +from agent_runner import DockerAgent +from isolation_engine import BaselineController, Isolation, play_game +from sqlalchemy import func, select +from sqlalchemy.orm import Session + +from .config import Settings +from .models import Agent, Submission, new_id + +ALLOWED_IMPORT_ROOTS = { + "collections", + "dataclasses", + "functools", + "heapq", + "itertools", + "math", + "operator", + "queue", + "random", + "statistics", + "typing", + "isolation_engine", +} + + +@dataclass(frozen=True) +class PreparedSubmission: + source: bytes + source_hash: str + validation: dict[str, Any] + + @property + def accepted(self) -> bool: + return bool(self.validation.get("accepted")) + + +def normalize_source(source: str, max_bytes: int) -> bytes: + normalized = source.replace("\r\n", "\n").replace("\r", "\n") + encoded = normalized.encode("utf-8") + if len(encoded) > max_bytes: + raise ValueError(f"agent.py exceeds the {max_bytes} byte beta limit") + if b"\x00" in encoded: + raise ValueError("agent.py contains a null byte") + return encoded + + +def static_source_checks(source: bytes) -> dict[str, Any]: + checks: dict[str, Any] = { + "syntax_valid": False, + "custom_player_present": False, + "get_action_present": False, + "imports_allowed": False, + } + try: + tree = ast.parse(source.decode("utf-8"), filename="agent.py") + except (SyntaxError, UnicodeDecodeError) as error: + checks["error"] = f"{type(error).__name__}: {error}" + return checks + checks["syntax_valid"] = True + player_class = next( + ( + node + for node in tree.body + if isinstance(node, ast.ClassDef) and node.name == "CustomPlayer" + ), + None, + ) + checks["custom_player_present"] = player_class is not None + checks["get_action_present"] = bool( + player_class + and any( + isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef) and node.name == "get_action" + for node in player_class.body + ) + ) + imports: list[str] = [] + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imports.extend(alias.name.split(".")[0] for alias in node.names) + elif isinstance(node, ast.ImportFrom) and node.module: + imports.append(node.module.split(".")[0]) + rejected = sorted({name for name in imports if name not in ALLOWED_IMPORT_ROOTS}) + checks["imports_allowed"] = not rejected + checks["rejected_imports"] = rejected + return checks + + +def prepare_submission(source: str, settings: Settings) -> PreparedSubmission: + """Validate source without importing it into this Python process.""" + + source_bytes = normalize_source(source, settings.max_source_bytes) + source_hash = hashlib.sha256(source_bytes).hexdigest() + static = static_source_checks(source_bytes) + validation: dict[str, Any] = { + "accepted": False, + "source_hash": source_hash, + "static_contract": static, + "sample_states": {}, + "scrimmage": {}, + "errors": [], + } + required = ( + "syntax_valid", + "custom_player_present", + "get_action_present", + "imports_allowed", + ) + if not all(static.get(name) for name in required): + validation["errors"].append("The Python contract or import policy is invalid.") + return PreparedSubmission(source_bytes, source_hash, validation) + + diagnostic = DockerAgent.diagnostic(settings.runner_image) + validation["runner"] = diagnostic + if not diagnostic["available"]: + validation["errors"].append(diagnostic["message"]) + return PreparedSubmission(source_bytes, source_hash, validation) + + with temporary_source(settings, source_bytes) as path: + try: + states = validation_states() + for label, state in states.items(): + controller = DockerAgent(path, settings.runner_image) + try: + controller.reset(state.player()) + response = controller.choose_action(state, settings.move_budget_ms) + legal_actions = state.actions() + legal = ( + response.action in legal_actions + if legal_actions + else response.action is None + ) + validation["sample_states"][label] = { + "legal_response": legal, + "action": response.action, + } + finally: + controller.close() + samples_passed = all( + item["legal_response"] for item in validation["sample_states"].values() + ) + if not samples_passed: + validation["errors"].append("The agent returned an illegal sample action.") + return PreparedSubmission(source_bytes, source_hash, validation) + + result = play_game( + DockerAgent(path, settings.runner_image), + BaselineController("greedy", seed=7), + time_limit_ms=settings.move_budget_ms, + ) + validation["scrimmage"] = { + "completed_normally": result.end_status == "game_over", + "end_status": result.end_status, + "losing_reason": result.losing_reason, + } + if result.end_status != "game_over": + validation["errors"].append(f"The Greedy scrimmage ended with {result.end_status}.") + return PreparedSubmission(source_bytes, source_hash, validation) + except Exception as error: + validation["errors"].append(f"{type(error).__name__}: {error}") + return PreparedSubmission(source_bytes, source_hash, validation) + + validation["accepted"] = True + return PreparedSubmission(source_bytes, source_hash, validation) + + +def save_new_agent( + session: Session, + settings: Settings, + *, + owner_name: str, + name: str, + prepared: PreparedSubmission, +) -> Agent: + if not prepared.accepted: + raise ValueError("only an accepted submission can be stored") + clean_owner = owner_name.strip() + clean_name = name.strip() + existing = session.scalar( + select(Agent).where( + func.lower(Agent.owner_name) == clean_owner.lower(), + func.lower(Agent.name) == clean_name.lower(), + ) + ) + if existing is not None: + raise ValueError("this owner already has an agent with that name") + agent = Agent(owner_name=clean_owner, name=clean_name) + session.add(agent) + session.flush() + save_submission(session, settings, agent=agent, prepared=prepared) + return agent + + +def save_submission( + session: Session, + settings: Settings, + *, + agent: Agent, + prepared: PreparedSubmission, +) -> Submission: + if not prepared.accepted: + raise ValueError("only an accepted submission can be stored") + number = ( + session.scalar(select(func.max(Submission.number)).where(Submission.agent_id == agent.id)) + or 0 + ) + 1 + submission_id = new_id() + directory = settings.source_root / agent.id + directory.mkdir(parents=True, exist_ok=True) + path = directory / f"{submission_id}.py" + path.write_bytes(prepared.source) + submission = Submission( + id=submission_id, + agent_id=agent.id, + number=number, + source_hash=prepared.source_hash, + source_path=str(path), + execution_kind="docker", + validation_result=prepared.validation, + ) + session.add(submission) + session.flush() + agent.active_submission_id = submission.id + return submission + + +def run_practice( + source: str, + baseline: str, + seed: int, + settings: Settings, +) -> dict[str, Any]: + source_bytes = normalize_source(source, settings.max_source_bytes) + static = static_source_checks(source_bytes) + required = ("syntax_valid", "custom_player_present", "get_action_present", "imports_allowed") + if not all(static.get(name) for name in required): + return { + "status": "invalid", + "result": {"reason": "The Python contract or import policy is invalid."}, + "replay": [], + "logs": [{"level": "error", "message": "Static source validation failed."}], + } + diagnostic = DockerAgent.diagnostic(settings.runner_image) + if not diagnostic["available"]: + return { + "status": "runner_unavailable", + "result": {"reason": diagnostic["message"]}, + "replay": [], + "logs": [{"level": "error", "message": diagnostic["message"]}], + } + with temporary_source(settings, source_bytes) as path: + result = play_game( + DockerAgent(path, settings.runner_image), + BaselineController(baseline, seed=seed), + time_limit_ms=settings.move_budget_ms, + ) + return { + "status": "complete" if result.end_status == "game_over" else result.end_status, + "result": { + "winner": "submitted" if result.winner_player == 0 else baseline, + "end_status": result.end_status, + "losing_reason": result.losing_reason, + "seed": seed, + "engine_version": result.engine_version, + "ruleset_version": result.ruleset_version, + }, + "replay": result.replay_dicts(), + "logs": [ + {"level": "info", "message": f"Opponent: {baseline.title()}."}, + {"level": "success", "message": f"Game ended: {result.end_status}."}, + ], + } + + +@contextmanager +def temporary_source(settings: Settings, source: bytes) -> Iterator[Path]: + settings.temp_root.mkdir(parents=True, exist_ok=True) + path = settings.temp_root / f"{new_id()}.py" + path.write_bytes(source) + try: + yield path + finally: + path.unlink(missing_ok=True) + + +def validation_states() -> dict[str, Isolation]: + empty = Isolation() + opening = empty.result(57) + midgame = opening.result(0) + for _ in range(4): + if midgame.terminal_test(): + break + midgame = midgame.result(midgame.actions()[0]) + terminal = Isolation() + while not terminal.terminal_test(): + terminal = terminal.result(terminal.actions()[0]) + return {"empty": empty, "opening": opening, "midgame": midgame, "terminal": terminal} diff --git a/Projects/3_Adversarial Search/apps/api/alembic/env.py b/Projects/3_Adversarial Search/apps/api/alembic/env.py new file mode 100644 index 00000000..f5019508 --- /dev/null +++ b/Projects/3_Adversarial Search/apps/api/alembic/env.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from agent_arena_api.config import settings +from agent_arena_api.models import Base +from alembic import context +from sqlalchemy import engine_from_config, pool + +config = context.config +config.set_main_option("sqlalchemy.url", settings.database_url) +target_metadata = Base.metadata + + +def run_migrations_offline() -> None: + context.configure( + url=settings.database_url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/Projects/3_Adversarial Search/apps/api/alembic/versions/0001_initial_beta.py b/Projects/3_Adversarial Search/apps/api/alembic/versions/0001_initial_beta.py new file mode 100644 index 00000000..5be24380 --- /dev/null +++ b/Projects/3_Adversarial Search/apps/api/alembic/versions/0001_initial_beta.py @@ -0,0 +1,96 @@ +"""Create the simplified Agent Arena beta schema. + +Revision ID: 0001_initial_beta +Revises: +""" + +import sqlalchemy as sa +from alembic import op + +revision = "0001_initial_beta" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.create_table( + "agents", + sa.Column("id", sa.String(length=36), primary_key=True), + sa.Column("owner_name", sa.String(length=80), nullable=False), + sa.Column("name", sa.String(length=100), nullable=False), + sa.Column("active_submission_id", sa.String(length=36), nullable=True), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.UniqueConstraint("owner_name", "name", name="uq_owner_agent_name"), + ) + op.create_index("ix_agents_owner_name", "agents", ["owner_name"]) + op.create_table( + "submissions", + sa.Column("id", sa.String(length=36), primary_key=True), + sa.Column("agent_id", sa.String(length=36), sa.ForeignKey("agents.id"), nullable=False), + sa.Column("number", sa.Integer(), nullable=False), + sa.Column("source_hash", sa.String(length=64), nullable=False), + sa.Column("source_path", sa.Text(), nullable=False), + sa.Column("execution_kind", sa.String(length=20), nullable=False), + sa.Column("validation_result", sa.JSON(), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.UniqueConstraint("agent_id", "number", name="uq_agent_submission"), + ) + op.create_index("ix_submissions_agent_id", "submissions", ["agent_id"]) + op.create_table( + "fixtures", + sa.Column("id", sa.String(length=36), primary_key=True), + sa.Column( + "submission_a_id", + sa.String(length=36), + sa.ForeignKey("submissions.id"), + nullable=False, + ), + sa.Column( + "submission_b_id", + sa.String(length=36), + sa.ForeignKey("submissions.id"), + nullable=False, + ), + sa.Column("status", sa.String(length=20), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("completed_at", sa.DateTime(timezone=True), nullable=True), + ) + op.create_index("ix_fixtures_submission_a_id", "fixtures", ["submission_a_id"]) + op.create_index("ix_fixtures_submission_b_id", "fixtures", ["submission_b_id"]) + op.create_index("ix_fixtures_status", "fixtures", ["status"]) + op.create_table( + "games", + sa.Column("id", sa.String(length=36), primary_key=True), + sa.Column("fixture_id", sa.String(length=36), sa.ForeignKey("fixtures.id"), nullable=False), + sa.Column("leg_number", sa.Integer(), nullable=False), + sa.Column( + "first_submission_id", + sa.String(length=36), + sa.ForeignKey("submissions.id"), + nullable=False, + ), + sa.Column( + "second_submission_id", + sa.String(length=36), + sa.ForeignKey("submissions.id"), + nullable=False, + ), + sa.Column("winner_submission_id", sa.String(length=36), nullable=True), + sa.Column("end_status", sa.String(length=30), nullable=False), + sa.Column("losing_reason", sa.Text(), nullable=False), + sa.Column("replay", sa.JSON(), nullable=False), + sa.Column("engine_version", sa.String(length=80), nullable=False), + sa.Column("ruleset_version", sa.String(length=80), nullable=False), + sa.Column("started_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("completed_at", sa.DateTime(timezone=True), nullable=False), + sa.UniqueConstraint("fixture_id", "leg_number", name="uq_fixture_leg"), + ) + op.create_index("ix_games_fixture_id", "games", ["fixture_id"]) + + +def downgrade() -> None: + op.drop_table("games") + op.drop_table("fixtures") + op.drop_table("submissions") + op.drop_table("agents") diff --git a/Projects/3_Adversarial Search/apps/web/eslint.config.js b/Projects/3_Adversarial Search/apps/web/eslint.config.js new file mode 100644 index 00000000..95903e0f --- /dev/null +++ b/Projects/3_Adversarial Search/apps/web/eslint.config.js @@ -0,0 +1,25 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist', 'coverage'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2022, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + }, + }, +) diff --git a/Projects/3_Adversarial Search/apps/web/index.html b/Projects/3_Adversarial Search/apps/web/index.html new file mode 100644 index 00000000..166aa06d --- /dev/null +++ b/Projects/3_Adversarial Search/apps/web/index.html @@ -0,0 +1,13 @@ + + + + + + + Agent Arena · Knight Isolation + + +
+ + + diff --git a/Projects/3_Adversarial Search/apps/web/package-lock.json b/Projects/3_Adversarial Search/apps/web/package-lock.json new file mode 100644 index 00000000..2086ab9b --- /dev/null +++ b/Projects/3_Adversarial Search/apps/web/package-lock.json @@ -0,0 +1,4679 @@ +{ + "name": "@agent-arena/web", + "version": "0.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@agent-arena/web", + "version": "0.2.0", + "dependencies": { + "@vitejs/plugin-react": "4.6.0", + "react": "19.1.0", + "react-dom": "19.1.0", + "vite": "7.3.6" + }, + "devDependencies": { + "@eslint/js": "9.30.1", + "@testing-library/jest-dom": "6.6.3", + "@testing-library/react": "16.3.0", + "@testing-library/user-event": "14.6.1", + "@types/node": "24.0.13", + "@types/react": "19.1.8", + "@types/react-dom": "19.1.6", + "eslint": "9.30.1", + "eslint-plugin-react-hooks": "5.2.0", + "eslint-plugin-react-refresh": "0.4.20", + "globals": "16.3.0", + "jsdom": "26.1.0", + "typescript": "5.8.3", + "typescript-eslint": "8.36.0", + "vitest": "3.2.7" + }, + "engines": { + "node": ">=22.17.0 <25" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "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==", + "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==", + "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==", + "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.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "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==", + "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==", + "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/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.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==", + "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.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.30.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz", + "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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==", + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.19", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz", + "integrity": "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", + "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.0.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.13.tgz", + "integrity": "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/react": { + "version": "19.1.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", + "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.36.0.tgz", + "integrity": "sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.36.0", + "@typescript-eslint/type-utils": "8.36.0", + "@typescript-eslint/utils": "8.36.0", + "@typescript-eslint/visitor-keys": "8.36.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.36.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.36.0.tgz", + "integrity": "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.36.0", + "@typescript-eslint/types": "8.36.0", + "@typescript-eslint/typescript-estree": "8.36.0", + "@typescript-eslint/visitor-keys": "8.36.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.36.0.tgz", + "integrity": "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.36.0", + "@typescript-eslint/types": "^8.36.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.36.0.tgz", + "integrity": "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.36.0", + "@typescript-eslint/visitor-keys": "8.36.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.36.0.tgz", + "integrity": "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.36.0.tgz", + "integrity": "sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.36.0", + "@typescript-eslint/utils": "8.36.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.36.0.tgz", + "integrity": "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.36.0.tgz", + "integrity": "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.36.0", + "@typescript-eslint/tsconfig-utils": "8.36.0", + "@typescript-eslint/types": "8.36.0", + "@typescript-eslint/visitor-keys": "8.36.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.36.0.tgz", + "integrity": "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.36.0", + "@typescript-eslint/types": "8.36.0", + "@typescript-eslint/typescript-estree": "8.36.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.36.0.tgz", + "integrity": "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.36.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.6.0.tgz", + "integrity": "sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.19", + "@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-beta.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.7", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "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.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001805", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", + "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", + "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/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.389", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz", + "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==", + "license": "ISC" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.30.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz", + "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.30.1", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", + "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "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==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "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/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/nwsapi": { + "version": "2.2.24", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz", + "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.18.tgz", + "integrity": "sha512-xdB1oSLHbz1vRWgCDalrCqEFTWzFlhqFC5tIHLMOSUIjhm3XXQ1qrFy8S/ESr1JYRRXqM3c1QFiMZUJdUTqyMQ==", + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "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/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.36.0.tgz", + "integrity": "sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.36.0", + "@typescript-eslint/parser": "8.36.0", + "@typescript-eslint/utils": "8.36.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "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/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", + "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.7", + "@vitest/mocker": "3.2.7", + "@vitest/pretty-format": "^3.2.7", + "@vitest/runner": "3.2.7", + "@vitest/snapshot": "3.2.7", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.7", + "@vitest/ui": "3.2.7", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/Projects/3_Adversarial Search/apps/web/package.json b/Projects/3_Adversarial Search/apps/web/package.json new file mode 100644 index 00000000..1c569bab --- /dev/null +++ b/Projects/3_Adversarial Search/apps/web/package.json @@ -0,0 +1,39 @@ +{ + "name": "@agent-arena/web", + "private": true, + "version": "0.2.0", + "type": "module", + "engines": { + "node": ">=22.17.0 <25" + }, + "scripts": { + "dev": "vite --host 127.0.0.1 --configLoader runner", + "build": "tsc --noEmit && vite build --configLoader runner", + "lint": "eslint .", + "typecheck": "tsc --noEmit --pretty false", + "test": "vitest --configLoader runner" + }, + "dependencies": { + "@vitejs/plugin-react": "4.6.0", + "vite": "7.3.6", + "react": "19.1.0", + "react-dom": "19.1.0" + }, + "devDependencies": { + "@eslint/js": "9.30.1", + "@testing-library/jest-dom": "6.6.3", + "@testing-library/react": "16.3.0", + "@testing-library/user-event": "14.6.1", + "@types/node": "24.0.13", + "@types/react": "19.1.8", + "@types/react-dom": "19.1.6", + "eslint": "9.30.1", + "eslint-plugin-react-hooks": "5.2.0", + "eslint-plugin-react-refresh": "0.4.20", + "globals": "16.3.0", + "jsdom": "26.1.0", + "typescript": "5.8.3", + "typescript-eslint": "8.36.0", + "vitest": "3.2.7" + } +} diff --git a/Projects/3_Adversarial Search/apps/web/src/App.test.tsx b/Projects/3_Adversarial Search/apps/web/src/App.test.tsx new file mode 100644 index 00000000..48a1875d --- /dev/null +++ b/Projects/3_Adversarial Search/apps/web/src/App.test.tsx @@ -0,0 +1,267 @@ +import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { beforeEach, describe, expect, test, vi } from 'vitest' + +import App from './App' + +const mockApi = vi.hoisted(() => ({ + health: vi.fn(), + rules: vi.fn(), + leaderboard: vi.fn(), + agents: vi.fn(), + agent: vi.fn(), + agentFixtures: vi.fn(), + featuredFixture: vi.fn(), + fixture: vi.fn(), + createAgent: vi.fn(), + createSubmission: vi.fn(), + practice: vi.fn(), + runRound: vi.fn(), +})) + +vi.mock('./api', () => ({ api: mockApi })) + +const blankBoard = '41523161203939122082683632224299007' +const replay = [ + { + ply_index: 0, + active_player: 0, + board: blankBoard, + player_locations: [null, null], + chosen_action: 57, + chosen_destination: 57, + legal_actions: [57], + elapsed_ms: 1, + stdout_excerpt: '', + stderr_excerpt: '', + outcome: null, + }, + { + ply_index: 1, + active_player: 1, + board: blankBoard, + player_locations: [57, null], + chosen_action: null, + chosen_destination: null, + legal_actions: [], + elapsed_ms: 0, + stdout_excerpt: '', + stderr_excerpt: '', + outcome: 'game_over', + }, +] +const rules = { + game: 'knight_isolation', + board_width: 11, + board_height: 9, + runtime: 'Python 3.12', + move_budget_ms: 150, + fixture_games: 2, + points: { win: 3, draw: 1, loss: 0 }, + engine_version: 'engine-v1', + ruleset_version: 'rules-v1', + demo_owner_name: 'oualid', + minimum_ranked_fixtures: 10, +} +const table = [{ + rank: 1, + agent_id: 'agent-1', + agent_name: 'Atlas-AB', + owner_name: 'oualid', + submission_id: 'submission-1', + submission_number: 2, + fixtures: 12, + wins: 2, + draws: 1, + losses: 0, + points: 25, + points_per_fixture: 2.083, + qualified: true, + recent_form: ['W', 'D'], +}] +const submission = { + id: 'submission-1', + agent_id: 'agent-1', + number: 2, + source_hash: 'a'.repeat(64), + execution_kind: 'docker', + validation_result: { accepted: true }, + created_at: '2026-07-13T00:00:00Z', + is_active: true, +} +const agents = [{ + id: 'agent-1', + owner_name: 'oualid', + name: 'Atlas-AB', + active_submission_id: 'submission-1', + created_at: '2026-07-13T00:00:00Z', + submissions: [submission, { ...submission, id: 'old', number: 1, is_active: false }], + record: { fixtures: 3, wins: 2, draws: 1, losses: 0, points: 7 }, +}] +const fixtureHistory = [{ + id: 'fixture-1', + submission_number: 2, + opponent_name: 'GreedyFox', + opponent_submission_number: 1, + outcome: 'W', + score: '3-0', + completed_at: '2026-07-13T00:00:00Z', +}] +const fixture = { + id: 'fixture-1', + submission_a_id: 'submission-1', + submission_b_id: 'submission-2', + agent_a_name: 'Atlas-AB', + agent_b_name: 'GreedyFox', + status: 'complete', + winner_submission_id: 'submission-1', + is_draw: false, + points_a: 3, + points_b: 0, + games: [1, 2].map((leg) => ({ + id: `game-${leg}`, + leg_number: leg, + first_submission_id: leg === 1 ? 'submission-1' : 'submission-2', + second_submission_id: leg === 1 ? 'submission-2' : 'submission-1', + winner_submission_id: 'submission-1', + end_status: 'game_over', + losing_reason: 'no legal moves', + replay, + engine_version: 'engine-v1', + ruleset_version: 'rules-v1', + })), +} +const completedPractice = { + status: 'complete', + result: { winner: 'submitted' }, + logs: [{ level: 'success', message: 'Real game complete.' }], + replay, +} + +beforeEach(() => { + window.history.replaceState(null, '', '/') + vi.clearAllMocks() + mockApi.rules.mockResolvedValue(rules) + mockApi.health.mockResolvedValue({ + status: 'ok', + runner: { available: true, code: 'ready', message: 'Runner ready.' }, + }) + mockApi.leaderboard.mockResolvedValue(table) + mockApi.agents.mockResolvedValue(agents) + mockApi.agent.mockResolvedValue(agents[0]) + mockApi.agentFixtures.mockResolvedValue(fixtureHistory) + mockApi.featuredFixture.mockResolvedValue(fixture) + mockApi.fixture.mockResolvedValue(fixture) + mockApi.practice.mockResolvedValue(completedPractice) + mockApi.runRound.mockResolvedValue({ fixture_ids: ['fixture-1'], status: 'complete' }) + mockApi.createAgent.mockResolvedValue(agents[0]) + mockApi.createSubmission.mockResolvedValue(agents[0]) +}) + +test('route navigation and editor-to-submission handoff preserve code', async () => { + const user = userEvent.setup() + render() + await user.click(screen.getByRole('button', { name: /Code & Play/ })) + const editor = screen.getByRole('textbox', { name: 'agent.py editor' }) + fireEvent.change(editor, { target: { value: 'class CustomPlayer:\n pass' } }) + await user.click(screen.getByRole('button', { name: 'Prepare submission' })) + expect(await screen.findByRole('heading', { name: 'Submit Agent' })).toBeInTheDocument() + expect(screen.getByRole('textbox', { name: 'Submission source' })).toHaveValue( + 'class CustomPlayer:\n pass', + ) +}) + +test('synchronous practice renders logs and replay controls', async () => { + const user = userEvent.setup() + render() + await user.click(screen.getByRole('button', { name: /Code & Play/ })) + await user.click(screen.getByRole('button', { name: 'Run practice' })) + expect(await screen.findByText('Real game complete.')).toBeInTheDocument() + expect(mockApi.practice).toHaveBeenCalled() + expect(screen.getAllByText('complete').length).toBeGreaterThan(0) + expect(screen.getByRole('slider', { name: 'Replay position' })).toBeInTheDocument() +}) + +test('submission is validated and activated in one visible action', async () => { + const user = userEvent.setup() + render() + await user.click(primaryNavigation().getByRole('button', { name: /Submit Agent/ })) + await user.click(screen.getByRole('button', { name: 'Validate and submit' })) + expect(await screen.findByText(/Accepted submission 2/)).toBeInTheDocument() + expect(mockApi.createAgent).toHaveBeenCalled() +}) + +test('competition round refreshes the leaderboard without opening fixture details', async () => { + const user = userEvent.setup() + render() + await user.click(primaryNavigation().getByRole('button', { name: /Leaderboard/ })) + await user.click(screen.getByRole('button', { name: 'Run competition round' })) + expect(await screen.findByText('Completed 1 fixtures.')).toBeInTheDocument() + expect(mockApi.runRound).toHaveBeenCalled() + expect(screen.queryByRole('region', { name: /Game 1.*replay/ })).not.toBeInTheDocument() +}) + +test('leaderboard summary opens a dedicated agent page with fixture history and replays', async () => { + const user = userEvent.setup() + render() + await user.click(primaryNavigation().getByRole('button', { name: /Leaderboard/ })) + expect(screen.queryByText('Recent fixtures')).not.toBeInTheDocument() + await user.click(screen.getByRole('button', { name: 'Open Agent page' })) + await waitFor(() => expect(mockApi.agent).toHaveBeenCalledWith('agent-1')) + expect(window.location.hash).toBe('#/agent/agent-1') + expect(await screen.findByText('Agent details and immutable competition history for @oualid.')).toBeInTheDocument() + await user.click(screen.getByRole('tab', { name: 'Fixture history' })) + await user.click(screen.getByRole('button', { name: /Submission 2 vs GreedyFox v1/ })) + const gameOne = within(screen.getByRole('region', { name: /Game 1.*replay/ })) + const gameTwo = within(screen.getByRole('region', { name: /Game 2.*replay/ })) + expect(gameOne.getByText('Atlas-AB').closest('.replay-player')).toHaveClass('p1') + expect(gameOne.getByLabelText('Game score 1 to 0')).toBeInTheDocument() + expect(gameTwo.getByText('Atlas-AB').closest('.replay-player')).toHaveClass('p2') + expect(gameTwo.getByLabelText('Game score 0 to 1')).toBeInTheDocument() +}) + +test('My Agents shows active and previous immutable submissions', async () => { + const user = userEvent.setup() + render() + await user.click(primaryNavigation().getByRole('button', { name: /My Agents/ })) + expect((await screen.findAllByText('src aaaaaaaaaaaa')).length).toBe(2) + expect(screen.getByText('previous')).toBeInTheDocument() + expect(screen.getByText('7 pts')).toBeInTheDocument() +}) + +describe.each(['timeout', 'invalid_action'])('practice failure %s', (failure) => { + test('is presented explicitly', async () => { + mockApi.practice.mockResolvedValue({ + ...completedPractice, + status: failure, + logs: [{ level: 'error', message: `Runner ended with ${failure}` }], + }) + const user = userEvent.setup() + render() + await user.click(screen.getByRole('button', { name: /Code & Play/ })) + await user.click(screen.getByRole('button', { name: 'Run practice' })) + expect(await screen.findByText(`Runner ended with ${failure}`)).toBeInTheDocument() + expect(screen.getAllByText(failure.replace('_', ' ')).length).toBeGreaterThan(0) + }) +}) + +test('malicious names and logs are rendered as inert text', async () => { + mockApi.leaderboard.mockResolvedValue([{ ...table[0], agent_name: '' }]) + mockApi.practice.mockResolvedValue({ + ...completedPractice, + logs: [{ level: 'error', message: '' }], + }) + const user = userEvent.setup() + render() + await user.click(screen.getByRole('button', { name: /Code & Play/ })) + await user.click(screen.getByRole('button', { name: 'Run practice' })) + expect(await screen.findByText('')).toBeInTheDocument() + expect(document.querySelector('script:not([type="module"])')).toBeNull() + await user.click(primaryNavigation().getByRole('button', { name: /Leaderboard/ })) + await waitFor(() => expect(screen.getAllByText('').length).toBeGreaterThan(0)) + expect(document.querySelector('img')).toBeNull() +}) + +function primaryNavigation() { + return within(screen.getByRole('navigation', { name: 'Primary navigation' })) +} diff --git a/Projects/3_Adversarial Search/apps/web/src/App.tsx b/Projects/3_Adversarial Search/apps/web/src/App.tsx new file mode 100644 index 00000000..a14bd0df --- /dev/null +++ b/Projects/3_Adversarial Search/apps/web/src/App.tsx @@ -0,0 +1,786 @@ +import { useCallback, useEffect, useState, type ReactNode } from 'react' + +import { api } from './api' +import { ReplayViewer, type ReplayPlayer } from './components/ReplayBoard' +import type { + Agent, + AgentFixtureHistoryEntry, + Fixture, + Game, + LeaderboardEntry, + PracticeResult, + Rules, + ViewName, +} from './types' + +type AppView = ViewName | 'agent' + +interface AppRoute { + view: AppView + agentId?: string +} + +const views: Array<{ id: ViewName; label: string; icon: string }> = [ + { id: 'overview', label: 'Overview', icon: '◫' }, + { id: 'code', label: 'Code & Play', icon: '⌨' }, + { id: 'leaderboard', label: 'Leaderboard', icon: '↗' }, + { id: 'agents', label: 'My Agents', icon: '◇' }, + { id: 'submit', label: 'Submit Agent', icon: '⇧' }, +] + +const starterCode = `class CustomPlayer: + """Return a legal action before the 150 ms deadline.""" + + def __init__(self, player_id=0): + self.player_id = player_id + self.context = None + + def get_action(self, state): + actions = state.actions() + if not actions: + return None + + return max(actions, key=lambda action: self.score(state.result(action))) + + def score(self, state): + own = state.liberties(state.locs[self.player_id]) + opponent = state.liberties(state.locs[1 - self.player_id]) + return len(own) - len(opponent) +` + +export default function App() { + const [route, navigate] = useHashRoute() + const view = route.view + const [rules, setRules] = useState(null) + const [runner, setRunner] = useState<{ available: boolean; message: string } | null>(null) + const [leaderboard, setLeaderboard] = useState([]) + const [agents, setAgents] = useState([]) + const [featured, setFeatured] = useState(null) + const [preparedSource, setPreparedSource] = useState(starterCode) + const [submissionTarget, setSubmissionTarget] = useState(null) + const [error, setError] = useState('') + + const refresh = useCallback(async () => { + try { + const [nextRules, health, table, localAgents, fixture] = await Promise.all([ + api.rules(), + api.health(), + api.leaderboard(), + api.agents(), + api.featuredFixture(), + ]) + setRules(nextRules) + setRunner(health.runner) + setLeaderboard(table) + setAgents(localAgents.filter((agent) => agent.owner_name === nextRules.demo_owner_name)) + setFeatured(fixture) + setError('') + } catch (loadError) { + setError(loadError instanceof Error ? loadError.message : 'The API could not be loaded.') + } + }, []) + + useEffect(() => { + void refresh() + }, [refresh]) + + const prepareSubmission = (source: string) => { + setPreparedSource(source) + setSubmissionTarget(null) + navigate('submit') + } + + const submitNewVersion = (agentId: string) => { + setSubmissionTarget(agentId) + navigate('submit') + } + + return ( +
+ + +
+
+
Knight Isolation / {views.find((item) => item.id === view)?.label ?? 'Agent'}
+
+ Points from completed fixtures + +
+
+ +
+ {error ?
API unavailable: {error}
: null} + {view === 'overview' ? ( + + ) : null} + {view === 'code' ? ( + + ) : null} + {view === 'leaderboard' ? ( + navigate('agent', agentId)} + onRefresh={refresh} + /> + ) : null} + {view === 'agent' && route.agentId ? ( + navigate('leaderboard')} /> + ) : null} + {view === 'agents' ? ( + + ) : null} + {view === 'submit' ? ( + + ) : null} +
+
+ + +
+ ) +} + +function Overview({ + rules, + table, + featured, + navigate, +}: { + rules: Rules | null + table: LeaderboardEntry[] + featured: Fixture | null + navigate: (view: ViewName) => void +}) { + return ( +
+
+
+ One game · one points table +

Submit once. Your Knight Isolation agent keeps competing.

+

Write a Python agent, test it against a trusted baseline, submit one immutable version, and inspect every mirrored fixture.

+
+ + +
+
+ +
+ +
+ {[ + ['01', 'Code', 'Implement CustomPlayer.get_action(state).'], + ['02', 'Practice', 'Run one real game against a baseline.'], + ['03', 'Submit', 'Validation succeeds before source is stored.'], + ['04', 'Compete', 'Mirrored games produce fixture points.'], + ].map(([number, title, copy]) => ( +
{number}{title}

{copy}

+ ))} +
+ + +
+ + +
+ + + +
+ ) +} + +function CodeAndPlay({ + runnerAvailable, + onPrepare, +}: { + runnerAvailable: boolean + onPrepare: (source: string) => void +}) { + const [source, setSource] = useState(starterCode) + const [baseline, setBaseline] = useState('greedy') + const [practice, setPractice] = useState(null) + const [running, setRunning] = useState(false) + const [tab, setTab] = useState<'goal' | 'rules' | 'api'>('goal') + + const runPractice = async () => { + setRunning(true) + setPractice(null) + try { + setPractice(await api.practice(source, baseline)) + } catch (runError) { + setPractice({ + status: 'exception', + result: {}, + replay: [], + logs: [{ level: 'error', message: runError instanceof Error ? runError.message : 'Practice request failed.' }], + }) + } finally { + setRunning(false) + } + } + + const status = running ? 'running' : practice?.status ?? 'idle' + const lines = source.split('\n').map((_, index) => index + 1).join('\n') + return ( +
+ +
+
+
+
+
YourAgent vs {titleCase(baseline)}Practice · seed 17 · no table points
+ {status.replace('_', ' ')} +
+ +
+
+
+ {(['goal', 'rules', 'api'] as const).map((name) => ( + + ))} +
+ {tab === 'goal' ? : null} + {tab === 'rules' ? : null} + {tab === 'api' ? : null} +
+
+
+
+
+
agent.pyPython 3.12
+
+ + +
+
+
+ +