mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 236f4fe2c0 | |||
| 588e838dc4 | |||
| 2550c08749 | |||
| 0651c51901 | |||
| 3ce19993bc | |||
| 26a9abbe82 | |||
| 240017add1 | |||
| b5958b069e | |||
| 59b8009d7a | |||
| b8b4f58a79 | |||
| fcb190281c | |||
| 9fcf900a23 | |||
| 06ad5e30c9 | |||
| 739044087b | |||
| fa041537c3 | |||
| 079f423a4b | |||
| f6060f9c53 | |||
| b7f234641c | |||
| 4ac0af699f | |||
| fb9a941722 | |||
| c05339cb2d | |||
| 2ef518f063 | |||
| fbd9280239 | |||
| 45ac6b839c | |||
| 8b59143174 | |||
| c7b8f5d0d1 | |||
| 09533d3cb9 | |||
| 00582a487c | |||
| 71a2c574d2 |
@@ -15,8 +15,6 @@ make build && make run FRONTEND_PORT=12000 FRONTEND_HOST=0.0.0.0 BACKEND_HOST=0.
|
||||
|
||||
IMPORTANT: Before making any changes to the codebase, ALWAYS run `make install-pre-commit-hooks` to ensure pre-commit hooks are properly installed.
|
||||
|
||||
|
||||
|
||||
Before pushing any changes, you MUST ensure that any lint errors or simple test errors have been fixed.
|
||||
|
||||
* If you've made changes to the backend, you should run `pre-commit run --config ./dev_config/python/.pre-commit-config.yaml` (this will run on staged files).
|
||||
@@ -32,6 +30,12 @@ then re-run the command to ensure it passes. Common issues include:
|
||||
- Trailing whitespace
|
||||
- Missing newlines at end of files
|
||||
|
||||
## Git Best Practices
|
||||
|
||||
- Prefer specific `git add <filename>` instead of `git add .` to avoid accidentally staging unintended files
|
||||
- Be especially careful with `git reset --hard` after staging files, as it will remove accidentally staged files
|
||||
- When remote has new changes, use `git fetch upstream && git rebase upstream/<branch>` on the same branch
|
||||
|
||||
## Repository Structure
|
||||
Backend:
|
||||
- Located in the `openhands` directory
|
||||
|
||||
+1
-1
@@ -159,7 +159,7 @@ poetry run pytest ./tests/unit/test_*.py
|
||||
To reduce build time (e.g., if no changes were made to the client-runtime component), you can use an existing Docker
|
||||
container image by setting the SANDBOX_RUNTIME_CONTAINER_IMAGE environment variable to the desired Docker image.
|
||||
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.49-nikolaik`
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.50-nikolaik`
|
||||
|
||||
## Develop inside Docker container
|
||||
|
||||
|
||||
@@ -62,17 +62,17 @@ system requirements and more information.
|
||||
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.50-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.50-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.50
|
||||
```
|
||||
|
||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||
|
||||
+3
-3
@@ -51,17 +51,17 @@ OpenHands也可以使用Docker在本地系统上运行。
|
||||
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.50-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.50-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.50
|
||||
```
|
||||
|
||||
> **注意**: 如果您在0.44版本之前使用过OpenHands,您可能需要运行 `mv ~/.openhands-state ~/.openhands` 来将对话历史迁移到新位置。
|
||||
|
||||
+3
-3
@@ -42,17 +42,17 @@ OpenHandsはDockerを利用してローカル環境でも実行できます。
|
||||
> 公共ネットワークで実行していますか?[Hardened Docker Installation Guide](https://docs.all-hands.dev/usage/runtimes/docker#hardened-docker-installation)を参照して、ネットワークバインディングの制限や追加のセキュリティ対策を実施してください。
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.50-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.50-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.50
|
||||
```
|
||||
|
||||
**注**: バージョン0.44以前のOpenHandsを使用していた場合は、会話履歴を移行するために `mv ~/.openhands-state ~/.openhands` を実行してください。
|
||||
|
||||
@@ -12,7 +12,7 @@ services:
|
||||
- SANDBOX_API_HOSTNAME=host.docker.internal
|
||||
- DOCKER_HOST_ADDR=host.docker.internal
|
||||
#
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.49-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.50-nikolaik}
|
||||
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ services:
|
||||
image: openhands:latest
|
||||
container_name: openhands-app-${DATE:-}
|
||||
environment:
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.50-nikolaik}
|
||||
#- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234} # enable this only if you want a specific non-root sandbox user but you will have to manually adjust permissions of ~/.openhands for this user
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -103,7 +103,7 @@ The conversation history will be saved in `~/.openhands/sessions`.
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.50-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -112,7 +112,7 @@ docker run -it \
|
||||
-v ~/.openhands:/.openhands \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.50 \
|
||||
python -m openhands.cli.main --override-cli-mode true
|
||||
```
|
||||
|
||||
@@ -153,6 +153,7 @@ You can use the following commands whenever the prompt (`>`) is displayed:
|
||||
| `/new` | Start a new conversation |
|
||||
| `/settings` | View and modify current LLM/agent settings |
|
||||
| `/resume` | Resume the agent if paused |
|
||||
| `/mcp` | Manage MCP server configuration and view connection errors |
|
||||
|
||||
#### Settings and Configuration
|
||||
|
||||
@@ -162,7 +163,7 @@ follow the prompts:
|
||||
- **Basic settings**: Choose a model/provider and enter your API key.
|
||||
- **Advanced settings**: Set custom endpoints, enable or disable confirmation mode, and configure memory condensation.
|
||||
|
||||
Settings can also be managed via the `config.toml` file.
|
||||
Settings can also be managed via the `config.toml` file in the current directory or `~/.openhands/config.toml`.
|
||||
|
||||
#### Repository Initialization
|
||||
|
||||
@@ -174,6 +175,41 @@ project details and structure. Use this when onboarding the agent to a new codeb
|
||||
You can pause the agent while it is running by pressing `Ctrl-P`. To continue the conversation after pausing, simply
|
||||
type `/resume` at the prompt.
|
||||
|
||||
#### MCP Server Management
|
||||
|
||||
To configure Model Context Protocol (MCP) servers, you can refer to the documentation on [MCP servers](../mcp) and use the `/mcp` command in the CLI. This command provides an interactive interface for managing Model Context Protocol (MCP) servers:
|
||||
|
||||
- **List configured servers**: View all currently configured MCP servers (SSE, Stdio, and SHTTP)
|
||||
- **Add new server**: Interactively add a new MCP server with guided prompts
|
||||
- **Remove server**: Remove an existing MCP server from your configuration
|
||||
- **View errors**: Display any connection errors that occurred during MCP server startup
|
||||
|
||||
This command modifies your `~/.openhands/config.toml` file and will prompt you to restart OpenHands for changes to take effect.
|
||||
|
||||
To enable the [Tavily MCP server](https://github.com/tavily-ai/tavily-mcp) search engine, you can set the `search_api_key` under the `[core]` section in the `~/.openhands/config.toml` file.
|
||||
|
||||
##### Example of the `config.toml` file with MCP server configuration:
|
||||
|
||||
```toml
|
||||
[core]
|
||||
search_api_key = "tvly-your-api-key-here"
|
||||
|
||||
[mcp]
|
||||
stdio_servers = [
|
||||
{name="fetch", command="uvx", args=["mcp-server-fetch"]},
|
||||
]
|
||||
|
||||
sse_servers = [
|
||||
# Basic SSE server with just a URL
|
||||
"http://example.com:8080/sse",
|
||||
]
|
||||
|
||||
shttp_servers = [
|
||||
# Streamable HTTP server with API key authentication
|
||||
{url="https://secure-example.com/mcp", api_key="your-api-key"}
|
||||
]
|
||||
```
|
||||
|
||||
## Tips and Troubleshooting
|
||||
|
||||
- Use `/help` at any time to see the list of available commands.
|
||||
|
||||
@@ -61,7 +61,7 @@ export GITHUB_TOKEN="your-token" # Required for repository operations
|
||||
# Run OpenHands
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.50-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -73,7 +73,7 @@ docker run -it \
|
||||
-v ~/.openhands:/.openhands \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.50 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
|
||||
|
||||
@@ -39,6 +39,12 @@ limits and monitor usage.
|
||||
- [mistralai/devstral-small](https://www.all-hands.dev/blog/devstral-a-new-state-of-the-art-open-model-for-coding-agents) (20 May 2025) -- also available through [OpenRouter](https://openrouter.ai/mistralai/devstral-small:free)
|
||||
- [all-hands/openhands-lm-32b-v0.1](https://www.all-hands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model) (31 March 2025) -- also available through [OpenRouter](https://openrouter.ai/all-hands/openhands-lm-32b-v0.1)
|
||||
|
||||
### Known Issues
|
||||
|
||||
<Warning>
|
||||
As of July 2025, there are known issues with Gemini 2.5 Pro conversations taking longer than normal with OpenHands. We are continuing to investigate.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
Most current local and open source models are not as powerful. When using such models, you may see long
|
||||
wait times between messages, poor responses, or errors about malformed JSON. OpenHands can only be as powerful as the
|
||||
|
||||
@@ -68,23 +68,23 @@ Download and install the LM Studio desktop app from [lmstudio.ai](https://lmstud
|
||||
1. Check [the installation guide](/usage/local-setup) and ensure all prerequisites are met before running OpenHands, then run:
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.50-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.50-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.50
|
||||
```
|
||||
|
||||
2. Wait until the server is running (see log below):
|
||||
```
|
||||
Digest: sha256:e72f9baecb458aedb9afc2cd5bc935118d1868719e55d50da73190d3a85c674f
|
||||
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.50
|
||||
Starting OpenHands...
|
||||
Running OpenHands as root
|
||||
14:22:13 - openhands:INFO: server_config.py:50 - Using config class None
|
||||
|
||||
@@ -67,17 +67,17 @@ A system with a modern processor and a minimum of **4GB RAM** is recommended to
|
||||
### Start the App
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.50-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.50-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.50
|
||||
```
|
||||
|
||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||
|
||||
+19
-15
@@ -10,6 +10,25 @@ Model Context Protocol (MCP) is a mechanism that allows OpenHands to communicate
|
||||
servers can provide additional functionality to the agent, such as specialized data processing, external API access,
|
||||
or custom tools. MCP is based on the open standard defined at [modelcontextprotocol.io](https://modelcontextprotocol.io).
|
||||
|
||||
<Note>
|
||||
MCP is currently not available on OpenHands Cloud. This feature is only available when running OpenHands locally.
|
||||
</Note>
|
||||
|
||||
### How MCP Works
|
||||
|
||||
When OpenHands starts, it:
|
||||
|
||||
1. Reads the MCP configuration.
|
||||
2. Connects to any configured SSE and SHTTP servers.
|
||||
3. Starts any configured stdio servers.
|
||||
4. Registers the tools provided by these servers with the agent.
|
||||
|
||||
The agent can then use these tools just like any built-in tool. When the agent calls an MCP tool:
|
||||
|
||||
1. OpenHands routes the call to the appropriate MCP server.
|
||||
2. The server processes the request and returns a response.
|
||||
3. OpenHands converts the response to an observation and presents it to the agent.
|
||||
|
||||
## Configuration
|
||||
|
||||
MCP configuration can be defined in:
|
||||
@@ -104,21 +123,6 @@ Stdio servers are configured using an object with the following properties:
|
||||
- Default: `{}`
|
||||
- Description: Environment variables to set for the server process
|
||||
|
||||
## How MCP Works
|
||||
|
||||
When OpenHands starts, it:
|
||||
|
||||
1. Reads the MCP configuration.
|
||||
2. Connects to any configured SSE and SHTTP servers.
|
||||
3. Starts any configured stdio servers.
|
||||
4. Registers the tools provided by these servers with the agent.
|
||||
|
||||
The agent can then use these tools just like any built-in tool. When the agent calls an MCP tool:
|
||||
|
||||
1. OpenHands routes the call to the appropriate MCP server.
|
||||
2. The server processes the request and returns a response.
|
||||
3. OpenHands converts the response to an observation and presents it to the agent.
|
||||
|
||||
## Transport Protocols
|
||||
|
||||
OpenHands supports three different MCP transport protocols:
|
||||
|
||||
+931
-52
File diff suppressed because it is too large
Load Diff
Generated
+272
-105
@@ -1,32 +1,32 @@
|
||||
{
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.49.0",
|
||||
"version": "0.50.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.49.0",
|
||||
"version": "0.50.0",
|
||||
"dependencies": {
|
||||
"@heroui/react": "^2.8.1",
|
||||
"@microlink/react-json-view": "^1.26.2",
|
||||
"@monaco-editor/react": "^4.7.0-rc.0",
|
||||
"@react-router/node": "^7.7.0",
|
||||
"@react-router/serve": "^7.7.0",
|
||||
"@react-types/shared": "^3.29.1",
|
||||
"@react-router/node": "^7.7.1",
|
||||
"@react-router/serve": "^7.7.1",
|
||||
"@react-types/shared": "^3.31.0",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"@stripe/react-stripe-js": "^3.7.0",
|
||||
"@stripe/stripe-js": "^7.5.0",
|
||||
"@stripe/react-stripe-js": "^3.8.0",
|
||||
"@stripe/stripe-js": "^7.6.1",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.4.0",
|
||||
"axios": "^1.10.0",
|
||||
"axios": "^1.11.0",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"framer-motion": "^12.23.6",
|
||||
"framer-motion": "^12.23.9",
|
||||
"i18next": "^25.3.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
@@ -34,16 +34,16 @@
|
||||
"jose": "^6.0.12",
|
||||
"lucide-react": "^0.525.0",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"posthog-js": "^1.257.1",
|
||||
"posthog-js": "^1.258.2",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-hot-toast": "^2.5.1",
|
||||
"react-i18next": "^15.6.0",
|
||||
"react-i18next": "^15.6.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router": "^7.7.0",
|
||||
"react-router": "^7.7.1",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"react-textarea-autosize": "^8.5.9",
|
||||
"remark-breaks": "^4.0.0",
|
||||
@@ -51,17 +51,17 @@
|
||||
"sirv-cli": "^3.0.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"vite": "^7.0.5",
|
||||
"vite": "^7.0.6",
|
||||
"web-vitals": "^5.0.3",
|
||||
"ws": "^8.18.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/parser": "^7.28.0",
|
||||
"@babel/traverse": "^7.28.0",
|
||||
"@babel/types": "^7.28.1",
|
||||
"@babel/types": "^7.28.2",
|
||||
"@mswjs/socket.io-binding": "^0.2.0",
|
||||
"@playwright/test": "^1.54.1",
|
||||
"@react-router/dev": "^7.7.0",
|
||||
"@react-router/dev": "^7.7.1",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tanstack/eslint-plugin-query": "^5.81.2",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
@@ -630,9 +630,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz",
|
||||
"integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==",
|
||||
"version": "7.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
|
||||
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.27.1"
|
||||
@@ -1399,6 +1399,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/accordion/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/alert": {
|
||||
"version": "2.2.23",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/alert/-/alert-2.2.23.tgz",
|
||||
@@ -1433,6 +1441,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/aria-utils/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/autocomplete": {
|
||||
"version": "2.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/autocomplete/-/autocomplete-2.3.25.tgz",
|
||||
@@ -1463,6 +1479,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/autocomplete/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/avatar": {
|
||||
"version": "2.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/avatar/-/avatar-2.2.19.tgz",
|
||||
@@ -1537,6 +1561,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/button/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/calendar": {
|
||||
"version": "2.2.23",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/calendar/-/calendar-2.2.23.tgz",
|
||||
@@ -1570,6 +1602,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/calendar/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/card": {
|
||||
"version": "2.2.22",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/card/-/card-2.2.22.tgz",
|
||||
@@ -1591,6 +1631,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/card/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/checkbox": {
|
||||
"version": "2.3.23",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/checkbox/-/checkbox-2.3.23.tgz",
|
||||
@@ -1616,6 +1664,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/checkbox/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/chip": {
|
||||
"version": "2.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/chip/-/chip-2.2.19.tgz",
|
||||
@@ -1671,6 +1727,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/date-input/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/date-picker": {
|
||||
"version": "2.3.24",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/date-picker/-/date-picker-2.3.24.tgz",
|
||||
@@ -1701,6 +1765,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/date-picker/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/divider": {
|
||||
"version": "2.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/divider/-/divider-2.2.16.tgz",
|
||||
@@ -1716,6 +1788,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/divider/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/dom-animation": {
|
||||
"version": "2.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/dom-animation/-/dom-animation-2.1.10.tgz",
|
||||
@@ -1783,6 +1863,14 @@
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/form/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/framer-utils": {
|
||||
"version": "2.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/framer-utils/-/framer-utils-2.1.19.tgz",
|
||||
@@ -1861,6 +1949,14 @@
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/input/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/kbd": {
|
||||
"version": "2.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/kbd/-/kbd-2.2.18.tgz",
|
||||
@@ -1919,6 +2015,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/listbox/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/menu": {
|
||||
"version": "2.2.22",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/menu/-/menu-2.2.22.tgz",
|
||||
@@ -1943,6 +2047,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/menu/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/modal": {
|
||||
"version": "2.2.20",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/modal/-/modal-2.2.20.tgz",
|
||||
@@ -2024,6 +2136,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/number-input/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/pagination": {
|
||||
"version": "2.2.21",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/pagination/-/pagination-2.2.21.tgz",
|
||||
@@ -2116,6 +2236,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/radio/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/react": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/react/-/react-2.8.1.tgz",
|
||||
@@ -2263,6 +2391,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/select/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/shared-icons": {
|
||||
"version": "2.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/shared-icons/-/shared-icons-2.1.10.tgz",
|
||||
@@ -2415,6 +2551,14 @@
|
||||
"react": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/system-rsc/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/system-rsc/node_modules/clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
@@ -2474,6 +2618,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/tabs/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/theme": {
|
||||
"version": "2.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/theme/-/theme-2.4.19.tgz",
|
||||
@@ -2573,6 +2725,14 @@
|
||||
"react": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/use-aria-accordion/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/use-aria-button": {
|
||||
"version": "2.2.17",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/use-aria-button/-/use-aria-button-2.2.17.tgz",
|
||||
@@ -2588,6 +2748,14 @@
|
||||
"react": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/use-aria-button/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/use-aria-link": {
|
||||
"version": "2.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/use-aria-link/-/use-aria-link-2.2.18.tgz",
|
||||
@@ -2603,6 +2771,14 @@
|
||||
"react": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/use-aria-link/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/use-aria-modal-overlay": {
|
||||
"version": "2.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/use-aria-modal-overlay/-/use-aria-modal-overlay-2.2.16.tgz",
|
||||
@@ -2642,6 +2818,14 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/use-aria-multiselect/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/use-aria-overlay": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/use-aria-overlay/-/use-aria-overlay-2.0.1.tgz",
|
||||
@@ -2657,6 +2841,14 @@
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/use-aria-overlay/node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/use-callback-ref": {
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/use-callback-ref/-/use-callback-ref-2.1.8.tgz",
|
||||
@@ -4103,9 +4295,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-router/dev": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.7.0.tgz",
|
||||
"integrity": "sha512-z6tJ0US20pS/YpaPz59SJgSH+1BJ6xvQmQ/u4Y4HM1uLOa4b3Mleg3KujqAvwGP5wkMkNFz3Ae2g6/kDTFyuCA==",
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.7.1.tgz",
|
||||
"integrity": "sha512-ByfgHmAyfx/JQYN/QwUx1sFJlBA5Z3HQAZ638wHSb+m6khWtHqSaKCvPqQh1P00wdEAeV3tX5L1aUM/ceCF6+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.27.7",
|
||||
@@ -4116,7 +4308,7 @@
|
||||
"@babel/traverse": "^7.27.7",
|
||||
"@babel/types": "^7.27.7",
|
||||
"@npmcli/package-json": "^4.0.1",
|
||||
"@react-router/node": "7.7.0",
|
||||
"@react-router/node": "7.7.1",
|
||||
"arg": "^5.0.1",
|
||||
"babel-dead-code-elimination": "^1.0.6",
|
||||
"chokidar": "^4.0.0",
|
||||
@@ -4128,7 +4320,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"pathe": "^1.1.2",
|
||||
"picocolors": "^1.1.1",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier": "^3.6.2",
|
||||
"react-refresh": "^0.14.0",
|
||||
"semver": "^7.3.7",
|
||||
"set-cookie-parser": "^2.6.0",
|
||||
@@ -4143,8 +4335,8 @@
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-router/serve": "^7.7.0",
|
||||
"react-router": "^7.7.0",
|
||||
"@react-router/serve": "^7.7.1",
|
||||
"react-router": "^7.7.1",
|
||||
"typescript": "^5.1.0",
|
||||
"vite": "^5.1.0 || ^6.0.0 || ^7.0.0",
|
||||
"wrangler": "^3.28.2 || ^4.0.0"
|
||||
@@ -4174,26 +4366,10 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-router/dev/node_modules/prettier": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-router/node": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-router/node/-/node-7.7.0.tgz",
|
||||
"integrity": "sha512-PTl4C+QjWsbTfp+9mybOzIH10ZM/pjZrAlcoxc/KGYxcfWDEe2GDFFBQN6nGZgJe/0SwSjHsVwqo2haMKgTbvQ==",
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-router/node/-/node-7.7.1.tgz",
|
||||
"integrity": "sha512-EHd6PEcw2nmcJmcYTPA0MmRWSqOaJ/meycfCp0ADA9T/6b7+fUHfr9XcNyf7UeZtYwu4zGyuYfPmLU5ic6Ugyg==",
|
||||
"dependencies": {
|
||||
"@mjackson/node-fetch-server": "^0.2.0"
|
||||
},
|
||||
@@ -4201,7 +4377,7 @@
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-router": "7.7.0",
|
||||
"react-router": "7.7.1",
|
||||
"typescript": "^5.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
@@ -4211,12 +4387,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-router/serve": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-router/serve/-/serve-7.7.0.tgz",
|
||||
"integrity": "sha512-XvJAY4Sgv7HxdSuLgkBP8bFXxfI97HJSk+p2BisdtK6JT/nSZugEe0gju4xAkgtsncNJJBVndJTcfUtTDNLTUQ==",
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-router/serve/-/serve-7.7.1.tgz",
|
||||
"integrity": "sha512-LyAiX+oI+6O6j2xWPUoKW+cgayUf3USBosSMv73Jtwi99XUhSDu2MUhM+BB+AbrYRubauZ83QpZTROiXoaf8jA==",
|
||||
"dependencies": {
|
||||
"@react-router/express": "7.7.0",
|
||||
"@react-router/node": "7.7.0",
|
||||
"@react-router/express": "7.7.1",
|
||||
"@react-router/node": "7.7.1",
|
||||
"compression": "^1.7.4",
|
||||
"express": "^4.19.2",
|
||||
"get-port": "5.1.1",
|
||||
@@ -4230,22 +4406,22 @@
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-router": "7.7.0"
|
||||
"react-router": "7.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-router/serve/node_modules/@react-router/express": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-router/express/-/express-7.7.0.tgz",
|
||||
"integrity": "sha512-R86v1qAbj3i/tG00gFM90P3nXR+B66qkp3bbaqm9VnTkbkqUCcHnVaQn64qBOl5g34FdJUMt84UsLS6v2mT/iQ==",
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-router/express/-/express-7.7.1.tgz",
|
||||
"integrity": "sha512-OEZwIM7i/KPSDjwVRg3LqeNIwG41U+SeFOwMjhZRFfyrnwghHfvWsDajf73r4ccMh+RRHcP1GIN6VSU3XZk7MA==",
|
||||
"dependencies": {
|
||||
"@react-router/node": "7.7.0"
|
||||
"@react-router/node": "7.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express": "^4.17.1 || ^5",
|
||||
"react-router": "7.7.0",
|
||||
"react-router": "7.7.1",
|
||||
"typescript": "^5.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
@@ -4802,10 +4978,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.31.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.31.0.tgz",
|
||||
"integrity": "sha512-ua5U6V66gDcbLZe4P2QeyNgPp4YWD1ymGA6j3n+s8CGExtrCPe64v+g4mvpT8Bnb985R96e4zFT61+m0YCwqMg==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
@@ -5238,10 +5413,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@stripe/react-stripe-js": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-3.7.0.tgz",
|
||||
"integrity": "sha512-PYls/2S9l0FF+2n0wHaEJsEU8x7CmBagiH7zYOsxbBlLIHEsqUIQ4MlIAbV9Zg6xwT8jlYdlRIyBTHmO3yM7kQ==",
|
||||
"license": "MIT",
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-3.8.0.tgz",
|
||||
"integrity": "sha512-yd130NpwvJpXK+Tleg5P3iYcg/pwFmPDMbrpBOi++S0h/dQtztsCktJswdAcaBMW143V7mj4PBhBANePP5nAzA==",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
@@ -5252,9 +5426,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stripe/stripe-js": {
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.5.0.tgz",
|
||||
"integrity": "sha512-Cq3KKe+G1o7PSBMbmrgpT2JgBeyH2THHr3RdIX2MqF7AnBuspIMgtZ3ktcCgP7kZsTMvnmWymr7zZCT1zeWbMw==",
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.6.1.tgz",
|
||||
"integrity": "sha512-BUDj5gujbtx53/Cexws0+aPrEBsKAN8ExPf9UfuTCivVU6ug2PjqI0zUeL1jon3795eOLlyqvCDjp6VNknjE0A==",
|
||||
"engines": {
|
||||
"node": ">=12.16"
|
||||
}
|
||||
@@ -7213,8 +7387,7 @@
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.21",
|
||||
@@ -7281,13 +7454,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
|
||||
"integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==",
|
||||
"license": "MIT",
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -7908,7 +8080,6 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
@@ -8375,7 +8546,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
@@ -9890,10 +10060,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
|
||||
"integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
|
||||
"license": "MIT",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
@@ -9936,11 +10105,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "12.23.6",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.6.tgz",
|
||||
"integrity": "sha512-dsJ389QImVE3lQvM8Mnk99/j8tiZDM/7706PCqvkQ8sSCnpmWxsgX+g0lj7r5OBVL0U36pIecCTBoIWcM2RuKw==",
|
||||
"version": "12.23.9",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.9.tgz",
|
||||
"integrity": "sha512-TqEHXj8LWfQSKqfdr5Y4mYltYLw96deu6/K9kGDd+ysqRJPNwF9nb5mZcrLmybHbU7gcJ+HQar41U3UTGanbbQ==",
|
||||
"dependencies": {
|
||||
"motion-dom": "^12.23.6",
|
||||
"motion-dom": "^12.23.9",
|
||||
"motion-utils": "^12.23.6",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
@@ -13411,9 +13580,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "12.23.6",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.6.tgz",
|
||||
"integrity": "sha512-G2w6Nw7ZOVSzcQmsdLc0doMe64O/Sbuc2bVAbgMz6oP/6/pRStKRiVRV4bQfHp5AHYAKEGhEdVHTM+R3FDgi5w==",
|
||||
"version": "12.23.9",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.9.tgz",
|
||||
"integrity": "sha512-6Sv++iWS8XMFCgU1qwKj9l4xuC47Hp4+2jvPfyTXkqDg2tTzSgX6nWKD4kNFXk0k7llO59LZTPuJigza4A2K1A==",
|
||||
"dependencies": {
|
||||
"motion-utils": "^12.23.6"
|
||||
}
|
||||
@@ -14265,9 +14434,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/posthog-js": {
|
||||
"version": "1.257.1",
|
||||
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.257.1.tgz",
|
||||
"integrity": "sha512-29kk3IO/LkPQ8E1cds6a2sWr5iN4BovgL+EMzRK9hQXbI6D3FJnQ7zLU6EUpktt6pHnqGpfO3BTEcflcDYkHBg==",
|
||||
"version": "1.258.2",
|
||||
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.258.2.tgz",
|
||||
"integrity": "sha512-XBSeiN4HjiYsy3tW5zss8WOJF2JXTQXAYw2wZ+zjqQuzzi7kkLEXjIgsVrBnt5Opwhqn0krZVsb0ZBw34dIiyQ==",
|
||||
"dependencies": {
|
||||
"core-js": "^3.38.1",
|
||||
"fflate": "^0.4.8",
|
||||
@@ -14631,10 +14800,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "15.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.6.0.tgz",
|
||||
"integrity": "sha512-W135dB0rDfiFmbMipC17nOhGdttO5mzH8BivY+2ybsQBbXvxWIwl3cmeH3T9d+YPBSJu/ouyJKFJTtkK7rJofw==",
|
||||
"license": "MIT",
|
||||
"version": "15.6.1",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.6.1.tgz",
|
||||
"integrity": "sha512-uGrzSsOUUe2sDBG/+FJq2J1MM+Y4368/QW8OLEKSFvnDflHBbZhSd1u3UkW0Z06rMhZmnB/AQrhCpYfE5/5XNg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
@@ -14739,9 +14907,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.7.0.tgz",
|
||||
"integrity": "sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw==",
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.7.1.tgz",
|
||||
"integrity": "sha512-jVKHXoWRIsD/qS6lvGveckwb862EekvapdHJN/cGmzw40KnJH5gg53ujOJ4qX6EKIK9LSBfFed/xiQ5yeXNrUA==",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0"
|
||||
@@ -17189,13 +17357,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.5.tgz",
|
||||
"integrity": "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==",
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz",
|
||||
"integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.6",
|
||||
"picomatch": "^4.0.2",
|
||||
"picomatch": "^4.0.3",
|
||||
"postcss": "^8.5.6",
|
||||
"rollup": "^4.40.0",
|
||||
"tinyglobby": "^0.2.14"
|
||||
@@ -17355,10 +17523,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"license": "MIT",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
||||
+14
-14
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.49.0",
|
||||
"version": "0.50.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
@@ -10,22 +10,22 @@
|
||||
"@heroui/react": "^2.8.1",
|
||||
"@microlink/react-json-view": "^1.26.2",
|
||||
"@monaco-editor/react": "^4.7.0-rc.0",
|
||||
"@react-router/node": "^7.7.0",
|
||||
"@react-router/serve": "^7.7.0",
|
||||
"@react-types/shared": "^3.29.1",
|
||||
"@react-router/node": "^7.7.1",
|
||||
"@react-router/serve": "^7.7.1",
|
||||
"@react-types/shared": "^3.31.0",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"@stripe/react-stripe-js": "^3.7.0",
|
||||
"@stripe/stripe-js": "^7.5.0",
|
||||
"@stripe/react-stripe-js": "^3.8.0",
|
||||
"@stripe/stripe-js": "^7.6.1",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.4.0",
|
||||
"axios": "^1.10.0",
|
||||
"axios": "^1.11.0",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"framer-motion": "^12.23.6",
|
||||
"framer-motion": "^12.23.9",
|
||||
"i18next": "^25.3.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
@@ -33,16 +33,16 @@
|
||||
"jose": "^6.0.12",
|
||||
"lucide-react": "^0.525.0",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"posthog-js": "^1.257.1",
|
||||
"posthog-js": "^1.258.2",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-hot-toast": "^2.5.1",
|
||||
"react-i18next": "^15.6.0",
|
||||
"react-i18next": "^15.6.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router": "^7.7.0",
|
||||
"react-router": "^7.7.1",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"react-textarea-autosize": "^8.5.9",
|
||||
"remark-breaks": "^4.0.0",
|
||||
@@ -50,7 +50,7 @@
|
||||
"sirv-cli": "^3.0.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"vite": "^7.0.5",
|
||||
"vite": "^7.0.6",
|
||||
"web-vitals": "^5.0.3",
|
||||
"ws": "^8.18.2"
|
||||
},
|
||||
@@ -82,10 +82,10 @@
|
||||
"devDependencies": {
|
||||
"@babel/parser": "^7.28.0",
|
||||
"@babel/traverse": "^7.28.0",
|
||||
"@babel/types": "^7.28.1",
|
||||
"@babel/types": "^7.28.2",
|
||||
"@mswjs/socket.io-binding": "^0.2.0",
|
||||
"@playwright/test": "^1.54.1",
|
||||
"@react-router/dev": "^7.7.0",
|
||||
"@react-router/dev": "^7.7.1",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tanstack/eslint-plugin-query": "^5.81.2",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
|
||||
+9
-5
@@ -1,6 +1,7 @@
|
||||
import { GitProviderIcon } from "#/components/shared/git-provider-icon";
|
||||
import { GitRepository } from "#/types/git";
|
||||
import { MicroagentManagementAddMicroagentButton } from "./microagent-management-add-microagent-button";
|
||||
import { TooltipButton } from "#/components/shared/buttons/tooltip-button";
|
||||
|
||||
interface MicroagentManagementAccordionTitleProps {
|
||||
repository: GitRepository;
|
||||
@@ -13,12 +14,15 @@ export function MicroagentManagementAccordionTitle({
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<GitProviderIcon gitProvider={repository.git_provider} />
|
||||
<div
|
||||
className="text-white text-base font-normal truncate max-w-[150px]"
|
||||
title={repository.full_name}
|
||||
<TooltipButton
|
||||
tooltip={repository.full_name}
|
||||
ariaLabel={repository.full_name}
|
||||
className="text-white text-base font-normal bg-transparent p-0 min-w-0 h-auto cursor-pointer truncate max-w-[232px]"
|
||||
testId="repository-name-tooltip"
|
||||
placement="bottom"
|
||||
>
|
||||
{repository.full_name}
|
||||
</div>
|
||||
<span>{repository.full_name}</span>
|
||||
</TooltipButton>
|
||||
</div>
|
||||
<MicroagentManagementAddMicroagentButton repository={repository} />
|
||||
</div>
|
||||
|
||||
+14
-8
@@ -7,6 +7,8 @@ import {
|
||||
} from "#/state/microagent-management-slice";
|
||||
import { RootState } from "#/store";
|
||||
import { GitRepository } from "#/types/git";
|
||||
import PlusIcon from "#/icons/plus.svg?react";
|
||||
import { TooltipButton } from "#/components/shared/buttons/tooltip-button";
|
||||
|
||||
interface MicroagentManagementAddMicroagentButtonProps {
|
||||
repository: GitRepository;
|
||||
@@ -23,19 +25,23 @@ export function MicroagentManagementAddMicroagentButton({
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
dispatch(setAddMicroagentModalVisible(!addMicroagentModalVisible));
|
||||
dispatch(setSelectedRepository(repository));
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="text-sm font-normal text-[#8480FF] cursor-pointer"
|
||||
onClick={handleClick}
|
||||
>
|
||||
{t(I18nKey.COMMON$ADD_MICROAGENT)}
|
||||
</button>
|
||||
<div onClick={handleClick}>
|
||||
<TooltipButton
|
||||
tooltip={t(I18nKey.COMMON$ADD_MICROAGENT)}
|
||||
ariaLabel={t(I18nKey.COMMON$ADD_MICROAGENT)}
|
||||
className="p-0 min-w-0 h-6 w-6 flex items-center justify-center bg-transparent cursor-pointer"
|
||||
testId="add-microagent-button"
|
||||
placement="bottom"
|
||||
>
|
||||
<PlusIcon width={22} height={22} />
|
||||
</TooltipButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+130
-45
@@ -2,11 +2,18 @@ import React, { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { MicroagentManagementSidebar } from "./microagent-management-sidebar";
|
||||
import { MicroagentManagementMain } from "./microagent-management-main";
|
||||
import { MicroagentManagementAddMicroagentModal } from "./microagent-management-add-microagent-modal";
|
||||
import { MicroagentManagementUpsertMicroagentModal } from "./microagent-management-upsert-microagent-modal";
|
||||
import { RootState } from "#/store";
|
||||
import { setAddMicroagentModalVisible } from "#/state/microagent-management-slice";
|
||||
import {
|
||||
setAddMicroagentModalVisible,
|
||||
setUpdateMicroagentModalVisible,
|
||||
setLearnThisRepoModalVisible,
|
||||
} from "#/state/microagent-management-slice";
|
||||
import { useCreateConversationAndSubscribeMultiple } from "#/hooks/use-create-conversation-and-subscribe-multiple";
|
||||
import { MicroagentFormData } from "#/types/microagent-management";
|
||||
import {
|
||||
LearnThisRepoFormData,
|
||||
MicroagentFormData,
|
||||
} from "#/types/microagent-management";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import { getPR, getProviderName, getPRShort } from "#/utils/utils";
|
||||
import {
|
||||
@@ -17,6 +24,7 @@ import {
|
||||
import { GitRepository } from "#/types/git";
|
||||
import { queryClient } from "#/query-client-config";
|
||||
import { Provider } from "#/types/settings";
|
||||
import { MicroagentManagementLearnThisRepoModal } from "./microagent-management-learn-this-repo-modal";
|
||||
|
||||
// Handle error events
|
||||
const isErrorEvent = (evt: unknown): evt is { error: true; message: string } =>
|
||||
@@ -52,33 +60,57 @@ const getConversationInstructions = (
|
||||
|
||||
- Step 1: Create a markdown file inside the .openhands/microagents folder with the name of the microagent (The microagent must be created in the .openhands/microagents folder and should be able to perform the described task when triggered).
|
||||
|
||||
- Step 2: Update the markdown file with the content below:
|
||||
- This is the instructions about what the microagent should do: ${formData.query}
|
||||
|
||||
${
|
||||
formData.triggers &&
|
||||
formData.triggers.length > 0 &&
|
||||
`
|
||||
---
|
||||
triggers:
|
||||
${formData.triggers.map((trigger: string) => ` - ${trigger}`).join("\n")}
|
||||
---
|
||||
formData.triggers && formData.triggers.length > 0
|
||||
? `
|
||||
- This is the triggers of the microagent: ${formData.triggers.join(", ")}
|
||||
`
|
||||
: "- Please be noted that the microagent doesn't have any triggers."
|
||||
}
|
||||
|
||||
${formData.query}
|
||||
- Step 2: Create a new branch for the repository ${repositoryName}, must avoid duplicated branches.
|
||||
|
||||
- Step 3: Create a new branch for the repository ${repositoryName}, must avoid duplicated branches.
|
||||
- Step 3: Please push the changes to your branch on ${getProviderName(gitProvider)} and create a ${pr}. Please create a meaningful branch name that describes the changes. If a ${pr} template exists in the repository, please follow it when creating the ${prShort} description.
|
||||
`;
|
||||
|
||||
- Step 4: Please push the changes to your branch on ${getProviderName(gitProvider)} and create a ${pr}. Please create a meaningful branch name that describes the changes. If a ${pr} template exists in the repository, please follow it when creating the ${prShort} description.
|
||||
const getUpdateConversationInstructions = (
|
||||
repositoryName: string,
|
||||
formData: MicroagentFormData,
|
||||
pr: string,
|
||||
prShort: string,
|
||||
gitProvider: Provider,
|
||||
) => `Update the microagent for the repository ${repositoryName} by following the steps below:
|
||||
|
||||
|
||||
- Step 1: Update the microagent. This is the path of the microagent: ${formData.microagentPath} (The updated microagent must be in the .openhands/microagents folder and should be able to perform the described task when triggered).
|
||||
|
||||
- This is the updated instructions about what the microagent should do: ${formData.query}
|
||||
|
||||
${
|
||||
formData.triggers && formData.triggers.length > 0
|
||||
? `
|
||||
- This is the triggers of the microagent: ${formData.triggers.join(", ")}
|
||||
`
|
||||
: "- Please be noted that the microagent doesn't have any triggers."
|
||||
}
|
||||
|
||||
- Step 2: Create a new branch for the repository ${repositoryName}, must avoid duplicated branches.
|
||||
|
||||
- Step 3: Please push the changes to your branch on ${getProviderName(gitProvider)} and create a ${pr}. Please create a meaningful branch name that describes the changes. If a ${pr} template exists in the repository, please follow it when creating the ${prShort} description.
|
||||
`;
|
||||
|
||||
export function MicroagentManagementContent() {
|
||||
// Responsive width state
|
||||
const [width, setWidth] = useState(window.innerWidth);
|
||||
|
||||
const { addMicroagentModalVisible, selectedRepository } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
const {
|
||||
addMicroagentModalVisible,
|
||||
updateMicroagentModalVisible,
|
||||
selectedRepository,
|
||||
learnThisRepoModalVisible,
|
||||
} = useSelector((state: RootState) => state.microagentManagement);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -96,8 +128,12 @@ export function MicroagentManagementContent() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const hideAddMicroagentModal = () => {
|
||||
dispatch(setAddMicroagentModalVisible(false));
|
||||
const hideUpsertMicroagentModal = (isUpdate: boolean = false) => {
|
||||
if (isUpdate) {
|
||||
dispatch(setUpdateMicroagentModalVisible(false));
|
||||
} else {
|
||||
dispatch(setAddMicroagentModalVisible(false));
|
||||
}
|
||||
};
|
||||
|
||||
// Reusable function to invalidate conversations list for a repository
|
||||
@@ -130,7 +166,10 @@ export function MicroagentManagementContent() {
|
||||
[invalidateConversationsList, selectedRepository],
|
||||
);
|
||||
|
||||
const handleCreateMicroagent = (formData: MicroagentFormData) => {
|
||||
const handleUpsertMicroagent = (
|
||||
formData: MicroagentFormData,
|
||||
isUpdate: boolean = false,
|
||||
) => {
|
||||
if (!selectedRepository || typeof selectedRepository !== "object") {
|
||||
return;
|
||||
}
|
||||
@@ -145,14 +184,22 @@ export function MicroagentManagementContent() {
|
||||
const pr = getPR(isGitLab);
|
||||
const prShort = getPRShort(isGitLab);
|
||||
|
||||
// Create conversation instructions for microagent generation
|
||||
const conversationInstructions = getConversationInstructions(
|
||||
repositoryName,
|
||||
formData,
|
||||
pr,
|
||||
prShort,
|
||||
gitProvider,
|
||||
);
|
||||
// Create conversation instructions for microagent generation or update
|
||||
const conversationInstructions = isUpdate
|
||||
? getUpdateConversationInstructions(
|
||||
repositoryName,
|
||||
formData,
|
||||
pr,
|
||||
prShort,
|
||||
gitProvider,
|
||||
)
|
||||
: getConversationInstructions(
|
||||
repositoryName,
|
||||
formData,
|
||||
pr,
|
||||
prShort,
|
||||
gitProvider,
|
||||
);
|
||||
|
||||
// Create the CreateMicroagent object
|
||||
const createMicroagent = {
|
||||
@@ -171,8 +218,6 @@ export function MicroagentManagementContent() {
|
||||
},
|
||||
createMicroagent,
|
||||
onSuccessCallback: () => {
|
||||
hideAddMicroagentModal();
|
||||
|
||||
// Invalidate conversations list to fetch the latest conversations for this repository
|
||||
invalidateConversationsList(repositoryName);
|
||||
|
||||
@@ -183,7 +228,7 @@ export function MicroagentManagementContent() {
|
||||
queryKey: ["repository-microagents", owner, repo],
|
||||
});
|
||||
|
||||
hideAddMicroagentModal();
|
||||
hideUpsertMicroagentModal(isUpdate);
|
||||
},
|
||||
onEventCallback: (event: unknown) => {
|
||||
// Handle conversation events for real-time status updates
|
||||
@@ -192,6 +237,58 @@ export function MicroagentManagementContent() {
|
||||
});
|
||||
};
|
||||
|
||||
const hideLearnThisRepoModal = () => {
|
||||
dispatch(setLearnThisRepoModalVisible(false));
|
||||
};
|
||||
|
||||
const handleLearnThisRepoConfirm = (formData: LearnThisRepoFormData) => {
|
||||
if (!selectedRepository || typeof selectedRepository !== "object") {
|
||||
return;
|
||||
}
|
||||
|
||||
const repository = selectedRepository as GitRepository;
|
||||
const repositoryName = repository.full_name;
|
||||
const gitProvider = repository.git_provider;
|
||||
|
||||
// Launch a new conversation to help the user understand the repo
|
||||
createConversationAndSubscribe({
|
||||
query: formData.query,
|
||||
conversationInstructions: formData.query,
|
||||
repository: {
|
||||
name: repositoryName,
|
||||
branch: formData.selectedBranch,
|
||||
gitProvider,
|
||||
},
|
||||
onSuccessCallback: () => {
|
||||
hideLearnThisRepoModal();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const renderModals = () => (
|
||||
<>
|
||||
{(addMicroagentModalVisible || updateMicroagentModalVisible) && (
|
||||
<MicroagentManagementUpsertMicroagentModal
|
||||
onConfirm={(formData) =>
|
||||
handleUpsertMicroagent(formData, updateMicroagentModalVisible)
|
||||
}
|
||||
onCancel={() =>
|
||||
hideUpsertMicroagentModal(updateMicroagentModalVisible)
|
||||
}
|
||||
isLoading={isPending}
|
||||
isUpdate={updateMicroagentModalVisible}
|
||||
/>
|
||||
)}
|
||||
{learnThisRepoModalVisible && (
|
||||
<MicroagentManagementLearnThisRepoModal
|
||||
onCancel={hideLearnThisRepoModal}
|
||||
onConfirm={handleLearnThisRepoConfirm}
|
||||
isLoading={isPending}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
if (width < 1024) {
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col gap-6">
|
||||
@@ -201,13 +298,7 @@ export function MicroagentManagementContent() {
|
||||
<div className="w-full rounded-lg border border-[#525252] bg-[#24272E] flex-1 min-h-[494px]">
|
||||
<MicroagentManagementMain />
|
||||
</div>
|
||||
{addMicroagentModalVisible && (
|
||||
<MicroagentManagementAddMicroagentModal
|
||||
onConfirm={handleCreateMicroagent}
|
||||
onCancel={hideAddMicroagentModal}
|
||||
isLoading={isPending}
|
||||
/>
|
||||
)}
|
||||
{renderModals()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -218,13 +309,7 @@ export function MicroagentManagementContent() {
|
||||
<div className="flex-1">
|
||||
<MicroagentManagementMain />
|
||||
</div>
|
||||
{addMicroagentModalVisible && (
|
||||
<MicroagentManagementAddMicroagentModal
|
||||
onConfirm={handleCreateMicroagent}
|
||||
onCancel={hideAddMicroagentModal}
|
||||
isLoading={isPending}
|
||||
/>
|
||||
)}
|
||||
{renderModals()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { FaCircleInfo } from "react-icons/fa6";
|
||||
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
|
||||
import { ModalBody } from "#/components/shared/modals/modal-body";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { RootState } from "#/store";
|
||||
import XIcon from "#/icons/x.svg?react";
|
||||
import { cn } from "#/utils/utils";
|
||||
import { LearnThisRepoFormData } from "#/types/microagent-management";
|
||||
import { Branch } from "#/types/git";
|
||||
import { useRepositoryBranches } from "#/hooks/query/use-repository-branches";
|
||||
import {
|
||||
BranchDropdown,
|
||||
BranchLoadingState,
|
||||
BranchErrorState,
|
||||
} from "../home/repository-selection";
|
||||
|
||||
interface MicroagentManagementLearnThisRepoModalProps {
|
||||
onConfirm: (formData: LearnThisRepoFormData) => void;
|
||||
onCancel: () => void;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export function MicroagentManagementLearnThisRepoModal({
|
||||
onConfirm,
|
||||
onCancel,
|
||||
isLoading = false,
|
||||
}: MicroagentManagementLearnThisRepoModalProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [query, setQuery] = useState<string>("");
|
||||
const [selectedBranch, setSelectedBranch] = useState<Branch | null>(null);
|
||||
|
||||
const { selectedRepository } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
// Add a ref to track if the branch was manually cleared by the user
|
||||
const branchManuallyClearedRef = useRef<boolean>(false);
|
||||
|
||||
const {
|
||||
data: branches,
|
||||
isLoading: isLoadingBranches,
|
||||
isError: isBranchesError,
|
||||
} = useRepositoryBranches(selectedRepository?.full_name || null);
|
||||
|
||||
const branchesItems = branches?.map((branch) => ({
|
||||
key: branch.name,
|
||||
label: branch.name,
|
||||
}));
|
||||
|
||||
// Auto-select main or master branch if it exists.
|
||||
useEffect(() => {
|
||||
if (
|
||||
branches &&
|
||||
branches.length > 0 &&
|
||||
!selectedBranch &&
|
||||
!isLoadingBranches
|
||||
) {
|
||||
// Look for main or master branch
|
||||
const mainBranch = branches.find((branch) => branch.name === "main");
|
||||
const masterBranch = branches.find((branch) => branch.name === "master");
|
||||
|
||||
// Select main if it exists, otherwise select master if it exists
|
||||
if (mainBranch) {
|
||||
setSelectedBranch(mainBranch);
|
||||
} else if (masterBranch) {
|
||||
setSelectedBranch(masterBranch);
|
||||
}
|
||||
}
|
||||
}, [branches, isLoadingBranches, selectedBranch]);
|
||||
|
||||
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (!query.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onConfirm({
|
||||
query: query.trim(),
|
||||
selectedBranch: selectedBranch?.name || "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!query.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onConfirm({
|
||||
query: query.trim(),
|
||||
selectedBranch: selectedBranch?.name || "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleBranchSelection = (key: React.Key | null) => {
|
||||
const selectedBranchObj = branches?.find((branch) => branch.name === key);
|
||||
setSelectedBranch(selectedBranchObj || null);
|
||||
// Reset the manually cleared flag when a branch is explicitly selected
|
||||
branchManuallyClearedRef.current = false;
|
||||
};
|
||||
|
||||
const handleBranchInputChange = (value: string) => {
|
||||
// Clear the selected branch if the input is empty or contains only whitespace
|
||||
// This fixes the issue where users can't delete the entire default branch name
|
||||
if (value === "" || value.trim() === "") {
|
||||
setSelectedBranch(null);
|
||||
// Set the flag to indicate that the branch was manually cleared
|
||||
branchManuallyClearedRef.current = true;
|
||||
} else {
|
||||
// Reset the flag when the user starts typing again
|
||||
branchManuallyClearedRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Render the appropriate UI for branch selector based on the loading/error state
|
||||
const renderBranchSelector = () => {
|
||||
if (!selectedRepository) {
|
||||
return (
|
||||
<BranchDropdown
|
||||
items={[]}
|
||||
onSelectionChange={() => {}}
|
||||
onInputChange={() => {}}
|
||||
isDisabled
|
||||
wrapperClassName="max-w-full w-full"
|
||||
label={t(I18nKey.REPOSITORY$SELECT_BRANCH)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoadingBranches) {
|
||||
return <BranchLoadingState wrapperClassName="max-w-full w-full" />;
|
||||
}
|
||||
|
||||
if (isBranchesError) {
|
||||
return <BranchErrorState wrapperClassName="max-w-full w-full" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<BranchDropdown
|
||||
items={branchesItems || []}
|
||||
onSelectionChange={handleBranchSelection}
|
||||
onInputChange={handleBranchInputChange}
|
||||
isDisabled={false}
|
||||
selectedKey={selectedBranch?.name}
|
||||
wrapperClassName="max-w-full w-full"
|
||||
label={t(I18nKey.REPOSITORY$SELECT_BRANCH)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalBackdrop onClose={onCancel}>
|
||||
<ModalBody
|
||||
className="items-start rounded-[12px] p-6 min-w-[611px]"
|
||||
data-testid="learn-this-repo-modal"
|
||||
>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2
|
||||
className="text-white text-xl font-medium"
|
||||
data-testid="modal-title"
|
||||
>
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_TITLE)}
|
||||
</h2>
|
||||
<a
|
||||
href="https://docs.all-hands.dev/usage/prompting/microagents-overview#microagents-overview"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-testid="modal-info-link"
|
||||
>
|
||||
<FaCircleInfo className="text-primary" />
|
||||
</a>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="cursor-pointer"
|
||||
data-testid="modal-close-button"
|
||||
>
|
||||
<XIcon width={24} height={24} color="#F9FBFE" />
|
||||
</button>
|
||||
</div>
|
||||
<span
|
||||
className="text-white text-sm font-normal"
|
||||
data-testid="modal-description"
|
||||
>
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_DESCRIPTION)}
|
||||
</span>
|
||||
</div>
|
||||
<form
|
||||
data-testid="learn-this-repo-form"
|
||||
onSubmit={onSubmit}
|
||||
className="flex flex-col gap-6 w-full"
|
||||
>
|
||||
<div data-testid="branch-selector-container">
|
||||
{renderBranchSelector()}
|
||||
</div>
|
||||
<label
|
||||
htmlFor="query-input"
|
||||
className="flex flex-col gap-2 w-full text-sm font-normal"
|
||||
>
|
||||
{t(
|
||||
I18nKey.MICROAGENT_MANAGEMENT$WHAT_YOU_WOULD_LIKE_TO_KNOW_ABOUT_THIS_REPO,
|
||||
)}
|
||||
<textarea
|
||||
required
|
||||
data-testid="query-input"
|
||||
name="query-input"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder={t(
|
||||
I18nKey.MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_KNOW_ABOUT_THIS_REPO,
|
||||
)}
|
||||
rows={6}
|
||||
className={cn(
|
||||
"bg-tertiary border border-[#717888] bg-[#454545] w-full rounded-sm p-2 placeholder:italic placeholder:text-tertiary-alt resize-none",
|
||||
"disabled:bg-[#2D2F36] disabled:border-[#2D2F36] disabled:cursor-not-allowed",
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
</form>
|
||||
<div
|
||||
className="flex items-center justify-end gap-2 w-full"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
data-testid="modal-actions"
|
||||
>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={onCancel}
|
||||
testId="cancel-button"
|
||||
>
|
||||
{t(I18nKey.BUTTON$CANCEL)}
|
||||
</BrandButton>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={handleConfirm}
|
||||
testId="confirm-button"
|
||||
isDisabled={
|
||||
!query.trim() ||
|
||||
isLoading ||
|
||||
isLoadingBranches ||
|
||||
!selectedBranch ||
|
||||
isBranchesError
|
||||
}
|
||||
>
|
||||
{isLoading || isLoadingBranches
|
||||
? t(I18nKey.HOME$LOADING)
|
||||
: t(I18nKey.MICROAGENT$LAUNCH)}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</ModalBody>
|
||||
</ModalBackdrop>
|
||||
);
|
||||
}
|
||||
+21
-10
@@ -1,25 +1,36 @@
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import {
|
||||
setLearnThisRepoModalVisible,
|
||||
setSelectedRepository,
|
||||
} from "#/state/microagent-management-slice";
|
||||
import { GitRepository } from "#/types/git";
|
||||
|
||||
interface MicroagentManagementLearnThisRepoProps {
|
||||
repositoryUrl: string;
|
||||
repository: GitRepository;
|
||||
}
|
||||
|
||||
export function MicroagentManagementLearnThisRepo({
|
||||
repositoryUrl,
|
||||
repository,
|
||||
}: MicroagentManagementLearnThisRepoProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleClick = () => {
|
||||
dispatch(setLearnThisRepoModalVisible(true));
|
||||
dispatch(setSelectedRepository(repository));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center rounded-lg bg-[#ffffff0d] border border-dashed border-[#ffffff4d] p-4 hover:bg-[#ffffff33] hover:border-[#C9B974] transition-all duration-300 cursor-pointer">
|
||||
<a
|
||||
className="text-[16px] font-normal text-[#8480FF]"
|
||||
href={repositoryUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-center rounded-lg bg-[#ffffff0d] border border-dashed border-[#ffffff4d] p-4 hover:bg-[#ffffff33] hover:border-[#C9B974] transition-all duration-300 cursor-pointer"
|
||||
onClick={handleClick}
|
||||
data-testid="learn-this-repo-trigger"
|
||||
>
|
||||
<span className="text-[16px] font-normal text-[#8480FF]">
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$LEARN_THIS_REPO)}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+12
-10
@@ -1,12 +1,11 @@
|
||||
import { useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Spinner } from "@heroui/react";
|
||||
import { MicroagentManagementMicroagentCard } from "./microagent-management-microagent-card";
|
||||
import { MicroagentManagementLearnThisRepo } from "./microagent-management-learn-this-repo";
|
||||
import { useRepositoryMicroagents } from "#/hooks/query/use-repository-microagents";
|
||||
import { useSearchConversations } from "#/hooks/query/use-search-conversations";
|
||||
import { LoadingSpinner } from "#/components/shared/loading-spinner";
|
||||
import { GitRepository } from "#/types/git";
|
||||
import { getGitProviderBaseUrl } from "#/utils/utils";
|
||||
import { RootState } from "#/store";
|
||||
import { setSelectedMicroagentItem } from "#/state/microagent-management-slice";
|
||||
|
||||
@@ -23,24 +22,27 @@ export function MicroagentManagementRepoMicroagents({
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { full_name: repositoryName, git_provider: gitProvider } = repository;
|
||||
const { full_name: repositoryName } = repository;
|
||||
|
||||
// Extract owner and repo from repositoryName (format: "owner/repo")
|
||||
const [owner, repo] = repositoryName.split("/");
|
||||
|
||||
const repositoryUrl = `${getGitProviderBaseUrl(gitProvider)}/${repositoryName}`;
|
||||
|
||||
const {
|
||||
data: microagents,
|
||||
isLoading: isLoadingMicroagents,
|
||||
isError: isErrorMicroagents,
|
||||
} = useRepositoryMicroagents(owner, repo);
|
||||
} = useRepositoryMicroagents(owner, repo, true);
|
||||
|
||||
const {
|
||||
data: conversations,
|
||||
isLoading: isLoadingConversations,
|
||||
isError: isErrorConversations,
|
||||
} = useSearchConversations(repositoryName, "microagent_management", 1000);
|
||||
} = useSearchConversations(
|
||||
repositoryName,
|
||||
"microagent_management",
|
||||
1000,
|
||||
true,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const hasConversations = conversations && conversations.length > 0;
|
||||
@@ -72,7 +74,7 @@ export function MicroagentManagementRepoMicroagents({
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="pb-4 flex justify-center">
|
||||
<LoadingSpinner size="small" />
|
||||
<Spinner size="sm" data-testid="loading-spinner" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -81,7 +83,7 @@ export function MicroagentManagementRepoMicroagents({
|
||||
if (isError) {
|
||||
return (
|
||||
<div className="pb-4">
|
||||
<MicroagentManagementLearnThisRepo repositoryUrl={repositoryUrl} />
|
||||
<MicroagentManagementLearnThisRepo repository={repository} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -93,7 +95,7 @@ export function MicroagentManagementRepoMicroagents({
|
||||
return (
|
||||
<div className="pb-4">
|
||||
{totalItems === 0 && (
|
||||
<MicroagentManagementLearnThisRepo repositoryUrl={repositoryUrl} />
|
||||
<MicroagentManagementLearnThisRepo repository={repository} />
|
||||
)}
|
||||
|
||||
{/* Render microagents */}
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ export function MicroagentManagementRepositories({
|
||||
className="w-full px-0 gap-3"
|
||||
itemClasses={{
|
||||
base: "shadow-none bg-transparent border border-[#ffffff40] rounded-[6px] cursor-pointer",
|
||||
trigger: "cursor-pointer",
|
||||
trigger: "cursor-pointer gap-1",
|
||||
}}
|
||||
selectionMode="multiple"
|
||||
>
|
||||
|
||||
+2
-2
@@ -1,6 +1,7 @@
|
||||
import { useEffect } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Spinner } from "@heroui/react";
|
||||
import { MicroagentManagementSidebarHeader } from "./microagent-management-sidebar-header";
|
||||
import { MicroagentManagementSidebarTabs } from "./microagent-management-sidebar-tabs";
|
||||
import { useUserRepositories } from "#/hooks/query/use-user-repositories";
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
setRepositories,
|
||||
} from "#/state/microagent-management-slice";
|
||||
import { GitRepository } from "#/types/git";
|
||||
import { LoadingSpinner } from "#/components/shared/loading-spinner";
|
||||
import { cn } from "#/utils/utils";
|
||||
|
||||
interface MicroagentManagementSidebarProps {
|
||||
@@ -58,7 +58,7 @@ export function MicroagentManagementSidebar({
|
||||
<MicroagentManagementSidebarHeader />
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col items-center justify-center gap-4 flex-1">
|
||||
<LoadingSpinner size="small" />
|
||||
<Spinner size="sm" />
|
||||
<span className="text-sm text-white">
|
||||
{t("HOME$LOADING_REPOSITORIES")}
|
||||
</span>
|
||||
|
||||
+45
-9
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { FaCircleInfo } from "react-icons/fa6";
|
||||
@@ -19,17 +19,19 @@ import {
|
||||
BranchErrorState,
|
||||
} from "../home/repository-selection";
|
||||
|
||||
interface MicroagentManagementAddMicroagentModalProps {
|
||||
interface MicroagentManagementUpsertMicroagentModalProps {
|
||||
onConfirm: (formData: MicroagentFormData) => void;
|
||||
onCancel: () => void;
|
||||
isLoading: boolean;
|
||||
isUpdate?: boolean;
|
||||
}
|
||||
|
||||
export function MicroagentManagementAddMicroagentModal({
|
||||
export function MicroagentManagementUpsertMicroagentModal({
|
||||
onConfirm,
|
||||
onCancel,
|
||||
isLoading = false,
|
||||
}: MicroagentManagementAddMicroagentModalProps) {
|
||||
isUpdate = false,
|
||||
}: MicroagentManagementUpsertMicroagentModalProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [triggers, setTriggers] = useState<string[]>([]);
|
||||
@@ -40,9 +42,23 @@ export function MicroagentManagementAddMicroagentModal({
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const { selectedMicroagentItem } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const { microagent } = selectedMicroagentItem ?? {};
|
||||
|
||||
// Add a ref to track if the branch was manually cleared by the user
|
||||
const branchManuallyClearedRef = useRef<boolean>(false);
|
||||
|
||||
// Populate form fields with existing microagent data when updating
|
||||
useEffect(() => {
|
||||
if (isUpdate && microagent) {
|
||||
setQuery(microagent.content);
|
||||
setTriggers(microagent.triggers || []);
|
||||
}
|
||||
}, [isUpdate, microagent]);
|
||||
|
||||
const {
|
||||
data: branches,
|
||||
isLoading: isLoadingBranches,
|
||||
@@ -75,9 +91,27 @@ export function MicroagentManagementAddMicroagentModal({
|
||||
}
|
||||
}, [branches, isLoadingBranches, selectedBranch]);
|
||||
|
||||
const modalTitle = selectedRepository
|
||||
? `${t(I18nKey.MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT_TO)} ${(selectedRepository as GitRepository).full_name}`
|
||||
: t(I18nKey.MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT);
|
||||
const modalTitle = useMemo(() => {
|
||||
if (isUpdate) {
|
||||
return t(I18nKey.MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT);
|
||||
}
|
||||
|
||||
if (selectedRepository) {
|
||||
return `${t(I18nKey.MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT_TO)} ${(selectedRepository as GitRepository).full_name}`;
|
||||
}
|
||||
|
||||
return t(I18nKey.MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT);
|
||||
}, [isUpdate, selectedRepository, t]);
|
||||
|
||||
const modalDescription = useMemo(() => {
|
||||
if (isUpdate) {
|
||||
return t(
|
||||
I18nKey.MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT_MODAL_DESCRIPTION,
|
||||
);
|
||||
}
|
||||
|
||||
return t(I18nKey.MICROAGENT_MANAGEMENT$ADD_MICROAGENT_MODAL_DESCRIPTION);
|
||||
}, [isUpdate, t]);
|
||||
|
||||
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
@@ -90,6 +124,7 @@ export function MicroagentManagementAddMicroagentModal({
|
||||
query: query.trim(),
|
||||
triggers,
|
||||
selectedBranch: selectedBranch?.name || "",
|
||||
microagentPath: microagent?.path || "",
|
||||
});
|
||||
};
|
||||
|
||||
@@ -102,6 +137,7 @@ export function MicroagentManagementAddMicroagentModal({
|
||||
query: query.trim(),
|
||||
triggers,
|
||||
selectedBranch: selectedBranch?.name || "",
|
||||
microagentPath: microagent?.path || "",
|
||||
});
|
||||
};
|
||||
|
||||
@@ -162,7 +198,7 @@ export function MicroagentManagementAddMicroagentModal({
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalBackdrop>
|
||||
<ModalBackdrop onClose={onCancel}>
|
||||
<ModalBody className="items-start rounded-[12px] p-6 min-w-[611px]">
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex justify-between items-center">
|
||||
@@ -181,7 +217,7 @@ export function MicroagentManagementAddMicroagentModal({
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-white text-sm font-normal">
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$ADD_MICROAGENT_MODAL_DESCRIPTION)}
|
||||
{modalDescription}
|
||||
</span>
|
||||
</div>
|
||||
<form
|
||||
+1
-1
@@ -34,7 +34,7 @@ export function MicroagentManagementViewMicroagentContent() {
|
||||
---
|
||||
|
||||
triggers:
|
||||
${microagent.triggers.map((trigger) => ` - ${trigger}`).join("\n")}
|
||||
${microagent.triggers.map((trigger) => ` - ${trigger}`).join("\n")}
|
||||
|
||||
---
|
||||
`;
|
||||
|
||||
+9
-3
@@ -1,12 +1,14 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { RootState } from "#/store";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
import { getProviderName, constructMicroagentUrl } from "#/utils/utils";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { setUpdateMicroagentModalVisible } from "#/state/microagent-management-slice";
|
||||
|
||||
export function MicroagentManagementViewMicroagentHeader() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { selectedMicroagentItem } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
@@ -29,6 +31,10 @@ export function MicroagentManagementViewMicroagentHeader() {
|
||||
microagent.path,
|
||||
);
|
||||
|
||||
const handleLearnSomethingNew = () => {
|
||||
dispatch(setUpdateMicroagentModalVisible(true));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between pb-2">
|
||||
<span className="text-sm text-[#ffffff99]">
|
||||
@@ -48,11 +54,11 @@ export function MicroagentManagementViewMicroagentHeader() {
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={() => {}}
|
||||
onClick={handleLearnSomethingNew}
|
||||
testId="learn-button"
|
||||
className="py-1 px-2"
|
||||
>
|
||||
{t(I18nKey.COMMON$LEARN)}
|
||||
{t(I18nKey.COMMON$LEARN_SOMETHING_NEW)}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
|
||||
export const useRepositoryMicroagents = (owner: string, repo: string) =>
|
||||
export const useRepositoryMicroagents = (
|
||||
owner: string,
|
||||
repo: string,
|
||||
cacheDisabled: boolean = false,
|
||||
) =>
|
||||
useQuery({
|
||||
queryKey: ["repository", "microagents", owner, repo],
|
||||
queryFn: () => OpenHands.getRepositoryMicroagents(owner, repo),
|
||||
enabled: !!owner && !!repo,
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
gcTime: 1000 * 60 * 15, // 15 minutes
|
||||
staleTime: cacheDisabled ? 0 : 1000 * 60 * 5, // 5 minutes
|
||||
gcTime: cacheDisabled ? 0 : 1000 * 60 * 15, // 15 minutes
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ export const useSearchConversations = (
|
||||
selectedRepository?: string,
|
||||
conversationTrigger?: string,
|
||||
limit: number = 20,
|
||||
cacheDisabled: boolean = false,
|
||||
) =>
|
||||
useQuery({
|
||||
queryKey: [
|
||||
@@ -21,6 +22,6 @@ export const useSearchConversations = (
|
||||
limit,
|
||||
),
|
||||
enabled: true, // Always enabled since parameters are optional
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
gcTime: 1000 * 60 * 15, // 15 minutes
|
||||
staleTime: cacheDisabled ? 0 : 1000 * 60 * 5, // 5 minutes
|
||||
gcTime: cacheDisabled ? 0 : 1000 * 60 * 15, // 15 minutes
|
||||
});
|
||||
|
||||
@@ -699,6 +699,7 @@ export enum I18nKey {
|
||||
MICROAGENT_MANAGEMENT$OPENHANDS_CAN_LEARN_ABOUT_REPOSITORIES = "MICROAGENT_MANAGEMENT$OPENHANDS_CAN_LEARN_ABOUT_REPOSITORIES",
|
||||
MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT_TO = "MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT_TO",
|
||||
MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT = "MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT",
|
||||
MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT = "MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT",
|
||||
MICROAGENT_MANAGEMENT$ADD_MICROAGENT_MODAL_DESCRIPTION = "MICROAGENT_MANAGEMENT$ADD_MICROAGENT_MODAL_DESCRIPTION",
|
||||
MICROAGENT_MANAGEMENT$WHAT_TO_DO = "MICROAGENT_MANAGEMENT$WHAT_TO_DO",
|
||||
MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_DO = "MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_DO",
|
||||
@@ -723,7 +724,13 @@ export enum I18nKey {
|
||||
COMMON$REVIEW_PR_IN = "COMMON$REVIEW_PR_IN",
|
||||
COMMON$EDIT_IN = "COMMON$EDIT_IN",
|
||||
COMMON$LEARN = "COMMON$LEARN",
|
||||
COMMON$LEARN_SOMETHING_NEW = "COMMON$LEARN_SOMETHING_NEW",
|
||||
COMMON$STARTING = "COMMON$STARTING",
|
||||
MICROAGENT_MANAGEMENT$ERROR = "MICROAGENT_MANAGEMENT$ERROR",
|
||||
MICROAGENT_MANAGEMENT$CONVERSATION_STOPPED = "MICROAGENT_MANAGEMENT$CONVERSATION_STOPPED",
|
||||
MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_TITLE = "MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_TITLE",
|
||||
MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_DESCRIPTION = "MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_DESCRIPTION",
|
||||
MICROAGENT_MANAGEMENT$WHAT_YOU_WOULD_LIKE_TO_KNOW_ABOUT_THIS_REPO = "MICROAGENT_MANAGEMENT$WHAT_YOU_WOULD_LIKE_TO_KNOW_ABOUT_THIS_REPO",
|
||||
MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_KNOW_ABOUT_THIS_REPO = "MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_KNOW_ABOUT_THIS_REPO",
|
||||
MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT_MODAL_DESCRIPTION = "MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT_MODAL_DESCRIPTION",
|
||||
}
|
||||
|
||||
@@ -11183,6 +11183,22 @@
|
||||
"de": "Microagent hinzufügen",
|
||||
"uk": "Додати мікроагента"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT": {
|
||||
"en": "Update microagent",
|
||||
"ja": "マイクロエージェントを更新",
|
||||
"zh-CN": "更新微代理",
|
||||
"zh-TW": "更新微代理",
|
||||
"ko-KR": "마이크로에이전트 업데이트",
|
||||
"no": "Oppdater mikroagent",
|
||||
"it": "Aggiorna microagent",
|
||||
"pt": "Atualizar microagente",
|
||||
"es": "Actualizar microagente",
|
||||
"ar": "تحديث الوكيل الصغير",
|
||||
"fr": "Mettre à jour le microagent",
|
||||
"tr": "Mikro ajanı güncelle",
|
||||
"de": "Microagent aktualisieren",
|
||||
"uk": "Оновити мікроагента"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$ADD_MICROAGENT_MODAL_DESCRIPTION": {
|
||||
"en": "OpenHands will create a new microagent based on your instructions.",
|
||||
"ja": "OpenHandsはあなたの指示に基づいて新しいマイクロエージェントを作成します。",
|
||||
@@ -11567,6 +11583,22 @@
|
||||
"de": "Lernen",
|
||||
"uk": "Вчитися"
|
||||
},
|
||||
"COMMON$LEARN_SOMETHING_NEW": {
|
||||
"en": "Learn something new",
|
||||
"ja": "新しいことを学ぶ",
|
||||
"zh-CN": "学习新东西",
|
||||
"zh-TW": "學習新東西",
|
||||
"ko-KR": "새로운 것을 배우기",
|
||||
"no": "Lær noe nytt",
|
||||
"it": "Impara qualcosa di nuovo",
|
||||
"pt": "Aprender algo novo",
|
||||
"es": "Aprender algo nuevo",
|
||||
"ar": "تعلم شيئًا جديدًا",
|
||||
"fr": "Apprendre quelque chose de nouveau",
|
||||
"tr": "Yeni bir şey öğren",
|
||||
"de": "Etwas Neues lernen",
|
||||
"uk": "Вивчити щось нове"
|
||||
},
|
||||
"COMMON$STARTING": {
|
||||
"en": "Starting",
|
||||
"ja": "開始中",
|
||||
@@ -11614,5 +11646,85 @@
|
||||
"tr": "Konuşma durduruldu.",
|
||||
"de": "Das Gespräch wurde gestoppt.",
|
||||
"uk": "Розмову зупинено."
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_TITLE": {
|
||||
"en": "Learn this repository?",
|
||||
"ja": "このリポジトリを学習しますか?",
|
||||
"zh-CN": "要学习此存储库吗?",
|
||||
"zh-TW": "要學習此存儲庫嗎?",
|
||||
"ko-KR": "이 저장소를 학습하시겠습니까?",
|
||||
"no": "Vil du lære dette depotet?",
|
||||
"it": "Vuoi imparare questo repository?",
|
||||
"pt": "Aprender este repositório?",
|
||||
"es": "¿Aprender este repositorio?",
|
||||
"ar": "هل تريد تعلم هذا المستودع؟",
|
||||
"fr": "Apprendre ce dépôt ?",
|
||||
"tr": "Bu depoyu öğrenmek ister misiniz?",
|
||||
"de": "Dieses Repository lernen?",
|
||||
"uk": "Вивчити цей репозиторій?"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_DESCRIPTION": {
|
||||
"en": "Do you want OpenHands to launch a new conversation to help you understand this repository?",
|
||||
"ja": "OpenHandsがこのリポジトリを理解するための新しい会話を開始しますか?",
|
||||
"zh-CN": "您想让OpenHands启动一个新对话来帮助您了解此存储库吗?",
|
||||
"zh-TW": "您想讓OpenHands啟動一個新對話來幫助您了解此存儲庫嗎?",
|
||||
"ko-KR": "OpenHands가 이 저장소를 이해하는 데 도움이 되는 새 대화를 시작하도록 하시겠습니까?",
|
||||
"no": "Vil du at OpenHands skal starte en ny samtale for å hjelpe deg med å forstå dette depotet?",
|
||||
"it": "Vuoi che OpenHands avvii una nuova conversazione per aiutarti a capire questo repository?",
|
||||
"pt": "Você quer que o OpenHands inicie uma nova conversa para ajudá-lo a entender este repositório?",
|
||||
"es": "¿Quieres que OpenHands inicie una nueva conversación para ayudarte a entender este repositorio?",
|
||||
"ar": "هل تريد أن يبدأ OpenHands محادثة جديدة لمساعدتك على فهم هذا المستودع؟",
|
||||
"fr": "Voulez-vous qu'OpenHands lance une nouvelle conversation pour vous aider à comprendre ce dépôt ?",
|
||||
"tr": "OpenHands'in bu depoyu anlamanıza yardımcı olacak yeni bir konuşma başlatmasını ister misiniz?",
|
||||
"de": "Möchten Sie, dass OpenHands eine neue Unterhaltung startet, um Ihnen dieses Repository zu erklären?",
|
||||
"uk": "Бажаєте, щоб OpenHands розпочав нову розмову, щоб допомогти вам зрозуміти цей репозиторій?"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$WHAT_YOU_WOULD_LIKE_TO_KNOW_ABOUT_THIS_REPO": {
|
||||
"en": "What would you like to know about this repository?",
|
||||
"ja": "このリポジトリについて何を知りたいですか?",
|
||||
"zh-CN": "您想了解此存储库的哪些内容?",
|
||||
"zh-TW": "您想了解此存儲庫的哪些內容?",
|
||||
"ko-KR": "이 저장소에 대해 무엇을 알고 싶으신가요?",
|
||||
"no": "Hva vil du vite om dette depotet?",
|
||||
"it": "Cosa vorresti sapere su questo repository?",
|
||||
"pt": "O que você gostaria de saber sobre este repositório?",
|
||||
"es": "¿Qué te gustaría saber sobre este repositorio?",
|
||||
"ar": "ماذا تريد أن تعرف عن هذا المستودع؟",
|
||||
"fr": "Que souhaitez-vous savoir sur ce dépôt ?",
|
||||
"tr": "Bu depo hakkında ne bilmek istersiniz?",
|
||||
"de": "Was möchten Sie über dieses Repository wissen?",
|
||||
"uk": "Що ви хотіли б дізнатися про цей репозиторій?"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_KNOW_ABOUT_THIS_REPO": {
|
||||
"en": "Describe what you would like to know about this repository.",
|
||||
"ja": "このリポジトリについて知りたいことを説明してください。",
|
||||
"zh-CN": "请描述您想了解此存储库的内容。",
|
||||
"zh-TW": "請描述您想了解此存儲庫的內容。",
|
||||
"ko-KR": "이 저장소에 대해 알고 싶은 내용을 설명하세요.",
|
||||
"no": "Beskriv hva du vil vite om dette depotet.",
|
||||
"it": "Descrivi cosa vorresti sapere su questo repository.",
|
||||
"pt": "Descreva o que você gostaria de saber sobre este repositório.",
|
||||
"es": "Describe lo que te gustaría saber sobre este repositorio.",
|
||||
"ar": "صف ما ترغب في معرفته عن هذا المستودع.",
|
||||
"fr": "Décrivez ce que vous souhaitez savoir sur ce dépôt.",
|
||||
"tr": "Bu depo hakkında ne bilmek istediğinizi açıklayın.",
|
||||
"de": "Beschreiben Sie, was Sie über dieses Repository wissen möchten.",
|
||||
"uk": "Опишіть, що ви хотіли б дізнатися про цей репозиторій."
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT_MODAL_DESCRIPTION": {
|
||||
"en": "OpenHands will update the microagent based on your instructions.",
|
||||
"ja": "OpenHandsはあなたの指示に基づいてマイクロエージェントを更新します。",
|
||||
"zh-CN": "OpenHands 将根据您的指示更新微代理。",
|
||||
"zh-TW": "OpenHands 將根據您的指示更新微代理。",
|
||||
"ko-KR": "OpenHands가 귀하의 지시에 따라 마이크로에이전트를 업데이트합니다.",
|
||||
"no": "OpenHands vil oppdatere mikroagenten basert på dine instruksjoner.",
|
||||
"it": "OpenHands aggiornerà il microagent in base alle tue istruzioni.",
|
||||
"pt": "O OpenHands atualizará o microagente com base nas suas instruções.",
|
||||
"es": "OpenHands actualizará el microagente según tus instrucciones.",
|
||||
"ar": "سيقوم OpenHands بتحديث الوكيل الصغير بناءً على تعليماتك.",
|
||||
"fr": "OpenHands mettra à jour le microagent selon vos instructions.",
|
||||
"tr": "OpenHands, talimatlarınıza göre mikro ajanı güncelleyecektir.",
|
||||
"de": "OpenHands aktualisiert den Microagenten basierend auf Ihren Anweisungen.",
|
||||
"uk": "OpenHands оновить мікроагента відповідно до ваших інструкцій."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,21 @@ export const microagentManagementSlice = createSlice({
|
||||
name: "microagentManagement",
|
||||
initialState: {
|
||||
addMicroagentModalVisible: false,
|
||||
updateMicroagentModalVisible: false,
|
||||
selectedRepository: null as GitRepository | null,
|
||||
personalRepositories: [] as GitRepository[],
|
||||
organizationRepositories: [] as GitRepository[],
|
||||
repositories: [] as GitRepository[],
|
||||
selectedMicroagentItem: null as IMicroagentItem | null,
|
||||
learnThisRepoModalVisible: false,
|
||||
},
|
||||
reducers: {
|
||||
setAddMicroagentModalVisible: (state, action) => {
|
||||
state.addMicroagentModalVisible = action.payload;
|
||||
},
|
||||
setUpdateMicroagentModalVisible: (state, action) => {
|
||||
state.updateMicroagentModalVisible = action.payload;
|
||||
},
|
||||
setSelectedRepository: (state, action) => {
|
||||
state.selectedRepository = action.payload;
|
||||
},
|
||||
@@ -31,16 +36,21 @@ export const microagentManagementSlice = createSlice({
|
||||
setSelectedMicroagentItem: (state, action) => {
|
||||
state.selectedMicroagentItem = action.payload;
|
||||
},
|
||||
setLearnThisRepoModalVisible: (state, action) => {
|
||||
state.learnThisRepoModalVisible = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setAddMicroagentModalVisible,
|
||||
setUpdateMicroagentModalVisible,
|
||||
setSelectedRepository,
|
||||
setPersonalRepositories,
|
||||
setOrganizationRepositories,
|
||||
setRepositories,
|
||||
setSelectedMicroagentItem,
|
||||
setLearnThisRepoModalVisible,
|
||||
} = microagentManagementSlice.actions;
|
||||
|
||||
export default microagentManagementSlice.reducer;
|
||||
|
||||
@@ -23,4 +23,10 @@ export interface MicroagentFormData {
|
||||
query: string;
|
||||
triggers: string[];
|
||||
selectedBranch: string;
|
||||
microagentPath: string;
|
||||
}
|
||||
|
||||
export interface LearnThisRepoFormData {
|
||||
query: string;
|
||||
selectedBranch: string;
|
||||
}
|
||||
|
||||
+185
-69
@@ -6,13 +6,12 @@
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.27.12",
|
||||
"clsx": "^2.1.1",
|
||||
"deep-equal": "^2.2.3",
|
||||
"focus-trap-react": "^11.0.4",
|
||||
"react-bootstrap-icons": "^1.11.6",
|
||||
"react-select": "^5.10.2",
|
||||
"sonner": "^2.0.6",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-scrollbar": "^4.0.2",
|
||||
"tailwindcss": "^4.1.10",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^4.0.0",
|
||||
@@ -23,6 +22,7 @@
|
||||
"@storybook/react-vite": "^9.0.12",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"@types/bun": "latest",
|
||||
"@types/deep-equal": "^1.0.4",
|
||||
"@vitejs/plugin-react": "^4.5.2",
|
||||
"@vitest/browser": "^3.2.4",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
@@ -33,8 +33,10 @@
|
||||
"vitest": "^3.2.4",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18.0.0",
|
||||
"react-dom": ">=18.0.0",
|
||||
"react": ">=19.1.0",
|
||||
"react-dom": ">=19.1.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.10",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -67,7 +69,7 @@
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.27.6", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.6" } }, "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug=="],
|
||||
"@babel/helpers": ["@babel/helpers@7.28.2", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.2" } }, "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.0" }, "bin": "./bin/babel-parser.js" }, "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g=="],
|
||||
|
||||
@@ -75,13 +77,13 @@
|
||||
|
||||
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.27.6", "", {}, "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q=="],
|
||||
"@babel/runtime": ["@babel/runtime@7.28.2", "", {}, "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.28.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/types": "^7.28.0", "debug": "^4.3.1" } }, "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.28.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ=="],
|
||||
"@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||
|
||||
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="],
|
||||
|
||||
@@ -109,57 +111,57 @@
|
||||
|
||||
"@emotion/weak-memoize": ["@emotion/weak-memoize@0.4.0", "", {}, "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.6", "", { "os": "aix", "cpu": "ppc64" }, "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw=="],
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.8", "", { "os": "aix", "cpu": "ppc64" }, "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.6", "", { "os": "android", "cpu": "arm" }, "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg=="],
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.8", "", { "os": "android", "cpu": "arm" }, "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.6", "", { "os": "android", "cpu": "arm64" }, "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA=="],
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.8", "", { "os": "android", "cpu": "arm64" }, "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.6", "", { "os": "android", "cpu": "x64" }, "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A=="],
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.8", "", { "os": "android", "cpu": "x64" }, "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA=="],
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg=="],
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.6", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg=="],
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.6", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ=="],
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.6", "", { "os": "linux", "cpu": "arm" }, "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw=="],
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.8", "", { "os": "linux", "cpu": "arm" }, "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ=="],
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.6", "", { "os": "linux", "cpu": "ia32" }, "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw=="],
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.8", "", { "os": "linux", "cpu": "ia32" }, "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.6", "", { "os": "linux", "cpu": "none" }, "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg=="],
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.6", "", { "os": "linux", "cpu": "none" }, "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw=="],
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.6", "", { "os": "linux", "cpu": "ppc64" }, "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw=="],
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.6", "", { "os": "linux", "cpu": "none" }, "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w=="],
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.6", "", { "os": "linux", "cpu": "s390x" }, "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw=="],
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.6", "", { "os": "linux", "cpu": "x64" }, "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig=="],
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.8", "", { "os": "linux", "cpu": "x64" }, "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.6", "", { "os": "none", "cpu": "arm64" }, "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q=="],
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.6", "", { "os": "none", "cpu": "x64" }, "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g=="],
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.8", "", { "os": "none", "cpu": "x64" }, "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.6", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg=="],
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.8", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.6", "", { "os": "openbsd", "cpu": "x64" }, "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw=="],
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.8", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.6", "", { "os": "none", "cpu": "arm64" }, "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA=="],
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.6", "", { "os": "sunos", "cpu": "x64" }, "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA=="],
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.8", "", { "os": "sunos", "cpu": "x64" }, "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q=="],
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ=="],
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.6", "", { "os": "win32", "cpu": "x64" }, "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA=="],
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.8", "", { "os": "win32", "cpu": "x64" }, "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw=="],
|
||||
|
||||
"@floating-ui/core": ["@floating-ui/core@1.7.2", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw=="],
|
||||
|
||||
@@ -189,9 +191,9 @@
|
||||
|
||||
"@mdx-js/react": ["@mdx-js/react@3.1.0", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ=="],
|
||||
|
||||
"@microsoft/api-extractor": ["@microsoft/api-extractor@7.52.8", "", { "dependencies": { "@microsoft/api-extractor-model": "7.30.6", "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", "@rushstack/node-core-library": "5.13.1", "@rushstack/rig-package": "0.5.3", "@rushstack/terminal": "0.15.3", "@rushstack/ts-command-line": "5.0.1", "lodash": "~4.17.15", "minimatch": "~3.0.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", "typescript": "5.8.2" }, "bin": { "api-extractor": "bin/api-extractor" } }, "sha512-cszYIcjiNscDoMB1CIKZ3My61+JOhpERGlGr54i6bocvGLrcL/wo9o+RNXMBrb7XgLtKaizZWUpqRduQuHQLdg=="],
|
||||
"@microsoft/api-extractor": ["@microsoft/api-extractor@7.52.9", "", { "dependencies": { "@microsoft/api-extractor-model": "7.30.7", "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", "@rushstack/node-core-library": "5.14.0", "@rushstack/rig-package": "0.5.3", "@rushstack/terminal": "0.15.4", "@rushstack/ts-command-line": "5.0.2", "lodash": "~4.17.15", "minimatch": "~3.0.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", "typescript": "5.8.2" }, "bin": { "api-extractor": "bin/api-extractor" } }, "sha512-313nyhc6DSSMVKD43jZK6Yp5XbliGw5vjN7QOw1FHzR1V6DQ67k4dzkd3BSxMtWcm+cEs1Ux8rmDqots6EABFA=="],
|
||||
|
||||
"@microsoft/api-extractor-model": ["@microsoft/api-extractor-model@7.30.6", "", { "dependencies": { "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", "@rushstack/node-core-library": "5.13.1" } }, "sha512-znmFn69wf/AIrwHya3fxX6uB5etSIn6vg4Q4RB/tb5VDDs1rqREc+AvMC/p19MUN13CZ7+V/8pkYPTj7q8tftg=="],
|
||||
"@microsoft/api-extractor-model": ["@microsoft/api-extractor-model@7.30.7", "", { "dependencies": { "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", "@rushstack/node-core-library": "5.14.0" } }, "sha512-TBbmSI2/BHpfR9YhQA7nH0nqVmGgJ0xH0Ex4D99/qBDAUpnhA2oikGmdXanbw9AWWY/ExBYIpkmY8dBHdla3YQ=="],
|
||||
|
||||
"@microsoft/tsdoc": ["@microsoft/tsdoc@0.15.1", "", {}, "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw=="],
|
||||
|
||||
@@ -203,7 +205,7 @@
|
||||
|
||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.19", "", {}, "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA=="],
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.2.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw=="],
|
||||
|
||||
@@ -247,35 +249,35 @@
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.45.1", "", { "os": "win32", "cpu": "x64" }, "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA=="],
|
||||
|
||||
"@rushstack/node-core-library": ["@rushstack/node-core-library@5.13.1", "", { "dependencies": { "ajv": "~8.13.0", "ajv-draft-04": "~1.0.0", "ajv-formats": "~3.0.1", "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", "semver": "~7.5.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-5yXhzPFGEkVc9Fu92wsNJ9jlvdwz4RNb2bMso+/+TH0nMm1jDDDsOIf4l8GAkPxGuwPw5DH24RliWVfSPhlW/Q=="],
|
||||
"@rushstack/node-core-library": ["@rushstack/node-core-library@5.14.0", "", { "dependencies": { "ajv": "~8.13.0", "ajv-draft-04": "~1.0.0", "ajv-formats": "~3.0.1", "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", "semver": "~7.5.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-eRong84/rwQUlATGFW3TMTYVyqL1vfW9Lf10PH+mVGfIb9HzU3h5AASNIw+axnBLjnD0n3rT5uQBwu9fvzATrg=="],
|
||||
|
||||
"@rushstack/rig-package": ["@rushstack/rig-package@0.5.3", "", { "dependencies": { "resolve": "~1.22.1", "strip-json-comments": "~3.1.1" } }, "sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow=="],
|
||||
|
||||
"@rushstack/terminal": ["@rushstack/terminal@0.15.3", "", { "dependencies": { "@rushstack/node-core-library": "5.13.1", "supports-color": "~8.1.1" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-DGJ0B2Vm69468kZCJkPj3AH5nN+nR9SPmC0rFHtzsS4lBQ7/dgOwtwVxYP7W9JPDMuRBkJ4KHmWKr036eJsj9g=="],
|
||||
"@rushstack/terminal": ["@rushstack/terminal@0.15.4", "", { "dependencies": { "@rushstack/node-core-library": "5.14.0", "supports-color": "~8.1.1" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-OQSThV0itlwVNHV6thoXiAYZlQh4Fgvie2CzxFABsbO2MWQsI4zOh3LRNigYSTrmS+ba2j0B3EObakPzf/x6Zg=="],
|
||||
|
||||
"@rushstack/ts-command-line": ["@rushstack/ts-command-line@5.0.1", "", { "dependencies": { "@rushstack/terminal": "0.15.3", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-bsbUucn41UXrQK7wgM8CNM/jagBytEyJqXw/umtI8d68vFm1Jwxh1OtLrlW7uGZgjCWiiPH6ooUNa1aVsuVr3Q=="],
|
||||
"@rushstack/ts-command-line": ["@rushstack/ts-command-line@5.0.2", "", { "dependencies": { "@rushstack/terminal": "0.15.4", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ=="],
|
||||
|
||||
"@storybook/addon-a11y": ["@storybook/addon-a11y@9.0.17", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^9.0.17" } }, "sha512-9cXNK3q/atx3hwJAt9HkJbd9vUxCXfKKiNNuSACbf8h9/j6u3jktulKOf6Xjc3B8lwn6ZpdK/x1HHZN2kTqsvg=="],
|
||||
"@storybook/addon-a11y": ["@storybook/addon-a11y@9.0.18", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^9.0.18" } }, "sha512-msbsTI9TmePQ5ElVclLi7ns5WaAntouJFaj9ElNugFWME21k68RiyXnioDjDfEoi/+y8tthQNNqjsHoX/Ev0Og=="],
|
||||
|
||||
"@storybook/addon-docs": ["@storybook/addon-docs@9.0.17", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "9.0.17", "@storybook/icons": "^1.2.12", "@storybook/react-dom-shim": "9.0.17", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^9.0.17" } }, "sha512-LOX/kKgQGnyulrqZHsvf77+ZoH/nSUaplGr5hvZglW/U6ak6fO9seJyXAzVKEnC6p+F8n02kFBZbi3s+znQhSg=="],
|
||||
"@storybook/addon-docs": ["@storybook/addon-docs@9.0.18", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "9.0.18", "@storybook/icons": "^1.2.12", "@storybook/react-dom-shim": "9.0.18", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^9.0.18" } }, "sha512-1mLhaRDx8s1JAF51o56OmwMnIsg4BOQJ8cn+4wbMjh14pDFALrovlFl/BpAXnV1VaZqHjCB4ZWuP+y5CwXEpeQ=="],
|
||||
|
||||
"@storybook/addon-onboarding": ["@storybook/addon-onboarding@9.0.17", "", { "peerDependencies": { "storybook": "^9.0.17" } }, "sha512-WoZZ8d58gP6uBu6OJ2K1GjBSM4+Kcr0I9lo0z3convzYqxrhfUm9pNEwVm57KCbVVyBbIKmevddCsSFoPC5u6Q=="],
|
||||
"@storybook/addon-onboarding": ["@storybook/addon-onboarding@9.0.18", "", { "peerDependencies": { "storybook": "^9.0.18" } }, "sha512-A079BfJ3g3wYOtAuq9cPf2l6JHo+6UzEw1A2AbSNBBNP4hKfXpHcLadIVwuyOxuKjDUWzY5f4dJa3hCMurHXGQ=="],
|
||||
|
||||
"@storybook/addon-vitest": ["@storybook/addon-vitest@9.0.17", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^1.4.0", "prompts": "^2.4.0", "ts-dedent": "^2.2.0" }, "peerDependencies": { "@vitest/browser": "^3.0.0", "@vitest/runner": "^3.0.0", "storybook": "^9.0.17", "vitest": "^3.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/runner", "vitest"] }, "sha512-eogqcGbACR1sTedBSE2SP/4QV1ruicHYEhYjBtoPIjvYgymN1g5KSuQNysLx4f0SvAzczrcNjX2WVVLX2DVyzA=="],
|
||||
"@storybook/addon-vitest": ["@storybook/addon-vitest@9.0.18", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^1.4.0", "prompts": "^2.4.0", "ts-dedent": "^2.2.0" }, "peerDependencies": { "@vitest/browser": "^3.0.0", "@vitest/runner": "^3.0.0", "storybook": "^9.0.18", "vitest": "^3.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/runner", "vitest"] }, "sha512-uPLh9H7kRho+raxyIBCm8Ymd3j0VPuWIQ1HSAkdx8itmNafNqs4HE67Z8Cfl259YzdWU/j5BhZqoiT62BCbIDw=="],
|
||||
|
||||
"@storybook/builder-vite": ["@storybook/builder-vite@9.0.17", "", { "dependencies": { "@storybook/csf-plugin": "9.0.17", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^9.0.17", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-lyuvgGhb0NaVk1tdB4xwzky6+YXQfxlxfNQqENYZ9uYQZdPfErMa4ZTXVQTV+CQHAa2NL+p/dG2JPAeu39e9UA=="],
|
||||
"@storybook/builder-vite": ["@storybook/builder-vite@9.0.18", "", { "dependencies": { "@storybook/csf-plugin": "9.0.18", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^9.0.18", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-lfbrozA6UPVizDrgbPEe04WMtxIraESwUkmwW3+Lxh8rKEUj5cXngcrJUW+meQNNaggdZZWEqeEtweuaLIR+Hg=="],
|
||||
|
||||
"@storybook/csf-plugin": ["@storybook/csf-plugin@9.0.17", "", { "dependencies": { "unplugin": "^1.3.1" }, "peerDependencies": { "storybook": "^9.0.17" } }, "sha512-6Q4eo1ObrLlsnB6bIt6T8+45XAb4to2pQGNrI7QPkLQRLrZinrJcNbLY7AGkyIoCOEsEbq08n09/nClQUbu8HA=="],
|
||||
"@storybook/csf-plugin": ["@storybook/csf-plugin@9.0.18", "", { "dependencies": { "unplugin": "^1.3.1" }, "peerDependencies": { "storybook": "^9.0.18" } }, "sha512-MQ3WwXnMua5sX0uYyuO7dC5WOWuJCLqf8CsOn3zQ2ptNoH6hD7DFx5ZOa1uD6VxIuJ3LkA+YqfSRBncomJoRnA=="],
|
||||
|
||||
"@storybook/global": ["@storybook/global@5.0.0", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="],
|
||||
|
||||
"@storybook/icons": ["@storybook/icons@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta" } }, "sha512-Td73IeJxOyalzvjQL+JXx72jlIYHgs+REaHiREOqfpo3A2AYYG71AUbcv+lg7mEDIweKVCxsMQ0UKo634c8XeA=="],
|
||||
|
||||
"@storybook/react": ["@storybook/react@9.0.17", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/react-dom-shim": "9.0.17" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.0.17", "typescript": ">= 4.9.x" }, "optionalPeers": ["typescript"] }, "sha512-wssao+uXg72OHtEJdQmmQJGdX90x/aU/6avoP3fgVgepWdZXVgciS9mnqHjKRF/vP+vPOlNQcJjojF/zTtq5qg=="],
|
||||
"@storybook/react": ["@storybook/react@9.0.18", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/react-dom-shim": "9.0.18" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.0.18", "typescript": ">= 4.9.x" }, "optionalPeers": ["typescript"] }, "sha512-CCH6Vj/O6I07PrhCHxc1pvCWYMfZhRzK7CVHAtrBP9xxnYA7OoXhM2wymuDogml5HW1BKtyVMeQ3oWZXFNgDXQ=="],
|
||||
|
||||
"@storybook/react-dom-shim": ["@storybook/react-dom-shim@9.0.17", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.0.17" } }, "sha512-ak/x/m6MDDxdE6rCDymTltaiQF3oiKrPHSwfM+YPgQR6MVmzTTs4+qaPfeev7FZEHq23IkfDMTmSTTJtX7Vs9A=="],
|
||||
"@storybook/react-dom-shim": ["@storybook/react-dom-shim@9.0.18", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.0.18" } }, "sha512-qGR/d9x9qWRRxITaBVQkMnb73kwOm+N8fkbZRxc7U4lxupXRvkMIDh247nn71SYVBnvbh6//AL7P6ghiPWZYjA=="],
|
||||
|
||||
"@storybook/react-vite": ["@storybook/react-vite@9.0.17", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "0.6.1", "@rollup/pluginutils": "^5.0.2", "@storybook/builder-vite": "9.0.17", "@storybook/react": "9.0.17", "find-up": "^7.0.0", "magic-string": "^0.30.0", "react-docgen": "^8.0.0", "resolve": "^1.22.8", "tsconfig-paths": "^4.2.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.0.17", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-wx1yKScni4ifOC/ccqpnnpceQbyF2xto+jHGsyua+M4UUCQdS2NYPDR8JFWp1YvBhVt2cQiD6SAltVGM9QLGnQ=="],
|
||||
"@storybook/react-vite": ["@storybook/react-vite@9.0.18", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "0.6.1", "@rollup/pluginutils": "^5.0.2", "@storybook/builder-vite": "9.0.18", "@storybook/react": "9.0.18", "find-up": "^7.0.0", "magic-string": "^0.30.0", "react-docgen": "^8.0.0", "resolve": "^1.22.8", "tsconfig-paths": "^4.2.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.0.18", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-dHzUoeY0/S35TvSYxCkPuBlNQZx4Zj9QDhAZ0qdv+nSll++uPgqSe2y2vF+2p+XVYhjDn+YX5LORv00YtuQezg=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="],
|
||||
|
||||
@@ -325,19 +327,21 @@
|
||||
|
||||
"@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
|
||||
"@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="],
|
||||
|
||||
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
||||
|
||||
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||
|
||||
"@types/deep-equal": ["@types/deep-equal@1.0.4", "", {}, "sha512-tqdiS4otQP4KmY0PR3u6KbZ5EWvhNdUoS/jc93UuK23C220lOZ/9TvjfxdPcKvqwwDVtmtSCrnr0p/2dirAxkA=="],
|
||||
|
||||
"@types/doctrine": ["@types/doctrine@0.0.9", "", {}, "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
|
||||
|
||||
"@types/node": ["@types/node@24.0.14", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw=="],
|
||||
"@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],
|
||||
|
||||
"@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
|
||||
|
||||
@@ -351,7 +355,7 @@
|
||||
|
||||
"@types/resolve": ["@types/resolve@1.20.6", "", {}, "sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.6.0", "", { "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" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" } }, "sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ=="],
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
|
||||
|
||||
"@vitest/browser": ["@vitest/browser@3.2.4", "", { "dependencies": { "@testing-library/dom": "^10.4.0", "@testing-library/user-event": "^14.6.1", "@vitest/mocker": "3.2.4", "@vitest/utils": "3.2.4", "magic-string": "^0.30.17", "sirv": "^3.0.1", "tinyrainbow": "^2.0.0", "ws": "^8.18.2" }, "peerDependencies": { "playwright": "*", "vitest": "3.2.4", "webdriverio": "^7.0.0 || ^8.0.0 || ^9.0.0" }, "optionalPeers": ["playwright", "webdriverio"] }, "sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw=="],
|
||||
|
||||
@@ -371,21 +375,21 @@
|
||||
|
||||
"@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
||||
|
||||
"@volar/language-core": ["@volar/language-core@2.4.19", "", { "dependencies": { "@volar/source-map": "2.4.19" } }, "sha512-i0aLpNA8DYZ2uG05t5K47nUWe+zvvrN9tfz16zS5pCJV9td8F0u+rVAOVSQ1ypufDLUD+ej9BH2/lmug4+lawQ=="],
|
||||
"@volar/language-core": ["@volar/language-core@2.4.20", "", { "dependencies": { "@volar/source-map": "2.4.20" } }, "sha512-dRDF1G33xaAIDqR6+mXUIjXYdu9vzSxlMGfMEwBxQsfY/JMUEXSpLTR057oTKlUQ2nIvCmP9k94A8h8z2VrNSA=="],
|
||||
|
||||
"@volar/source-map": ["@volar/source-map@2.4.19", "", {}, "sha512-ttWmO/Ld7r3ebIPPAYvAuSLrlJ96ZALPka44mD4sWA8bw2n9u7TGnMcaTUkiF0GLG8bq/K09beWmEAB1mqMy/A=="],
|
||||
"@volar/source-map": ["@volar/source-map@2.4.20", "", {}, "sha512-mVjmFQH8mC+nUaVwmbxoYUy8cww+abaO8dWzqPUjilsavjxH0jCJ3Mp8HFuHsdewZs2c+SP+EO7hCd8Z92whJg=="],
|
||||
|
||||
"@volar/typescript": ["@volar/typescript@2.4.19", "", { "dependencies": { "@volar/language-core": "2.4.19", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Xgo4QLuqusu2fqw4LCeoOY57d5UCn+fNUWZTg4PFubw07jBFFCSJIuJ7BDrRM3EZHDjCqq1RmUO9wkYihnM+8Q=="],
|
||||
"@volar/typescript": ["@volar/typescript@2.4.20", "", { "dependencies": { "@volar/language-core": "2.4.20", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Oc4DczPwQyXcVbd+5RsNEqX6ia0+w3p+klwdZQ6ZKhFjWoBP9PCPQYlKYRi/tDemWphW93P/Vv13vcE9I9D2GQ=="],
|
||||
|
||||
"@vue/compiler-core": ["@vue/compiler-core@3.5.17", "", { "dependencies": { "@babel/parser": "^7.27.5", "@vue/shared": "3.5.17", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA=="],
|
||||
"@vue/compiler-core": ["@vue/compiler-core@3.5.18", "", { "dependencies": { "@babel/parser": "^7.28.0", "@vue/shared": "3.5.18", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw=="],
|
||||
|
||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.17", "", { "dependencies": { "@vue/compiler-core": "3.5.17", "@vue/shared": "3.5.17" } }, "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ=="],
|
||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.18", "", { "dependencies": { "@vue/compiler-core": "3.5.18", "@vue/shared": "3.5.18" } }, "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A=="],
|
||||
|
||||
"@vue/compiler-vue2": ["@vue/compiler-vue2@2.7.16", "", { "dependencies": { "de-indent": "^1.0.2", "he": "^1.2.0" } }, "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A=="],
|
||||
|
||||
"@vue/language-core": ["@vue/language-core@2.2.0", "", { "dependencies": { "@volar/language-core": "~2.4.11", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", "alien-signals": "^0.4.9", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw=="],
|
||||
|
||||
"@vue/shared": ["@vue/shared@3.5.17", "", {}, "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg=="],
|
||||
"@vue/shared": ["@vue/shared@3.5.18", "", {}, "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
@@ -405,12 +409,16 @@
|
||||
|
||||
"aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
|
||||
|
||||
"array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
|
||||
|
||||
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||
|
||||
"ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="],
|
||||
|
||||
"ast-v8-to-istanbul": ["ast-v8-to-istanbul@0.3.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "estree-walker": "^3.0.3", "js-tokens": "^9.0.1" } }, "sha512-MuXMrSLVVoA6sYN/6Hke18vMzrT4TZNbZIj/hvh0fnYFpO+/kFXcLIaiPwXXWaQUPg4yJD8fj+lfJ7/1EBconw=="],
|
||||
|
||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||
|
||||
"axe-core": ["axe-core@4.10.3", "", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="],
|
||||
|
||||
"babel-plugin-macros": ["babel-plugin-macros@3.1.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
|
||||
@@ -423,10 +431,16 @@
|
||||
|
||||
"browserslist": ["browserslist@4.25.1", "", { "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
|
||||
"bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="],
|
||||
|
||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||
|
||||
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001727", "", {}, "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q=="],
|
||||
@@ -469,8 +483,14 @@
|
||||
|
||||
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
|
||||
|
||||
"deep-equal": ["deep-equal@2.2.3", "", { "dependencies": { "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.5", "es-get-iterator": "^1.1.3", "get-intrinsic": "^1.2.2", "is-arguments": "^1.1.1", "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "isarray": "^2.0.5", "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.5.1", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", "which-typed-array": "^1.1.13" } }, "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA=="],
|
||||
|
||||
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
|
||||
|
||||
"define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="],
|
||||
|
||||
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
|
||||
@@ -481,9 +501,11 @@
|
||||
|
||||
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.185", "", {}, "sha512-dYOZfUk57hSMPePoIQ1fZWl1Fkj+OshhEVuPacNKWzC1efe56OsHY3l/jCfiAgIICOU3VgOIdoq7ahg7r7n6MQ=="],
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.191", "", {}, "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
@@ -493,9 +515,17 @@
|
||||
|
||||
"error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-get-iterator": ["es-get-iterator@1.1.3", "", { "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", "has-symbols": "^1.0.3", "is-arguments": "^1.1.1", "is-map": "^2.0.2", "is-set": "^2.0.2", "is-string": "^1.0.7", "isarray": "^2.0.5", "stop-iteration-iterator": "^1.0.0" } }, "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw=="],
|
||||
|
||||
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
||||
|
||||
"esbuild": ["esbuild@0.25.6", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.6", "@esbuild/android-arm": "0.25.6", "@esbuild/android-arm64": "0.25.6", "@esbuild/android-x64": "0.25.6", "@esbuild/darwin-arm64": "0.25.6", "@esbuild/darwin-x64": "0.25.6", "@esbuild/freebsd-arm64": "0.25.6", "@esbuild/freebsd-x64": "0.25.6", "@esbuild/linux-arm": "0.25.6", "@esbuild/linux-arm64": "0.25.6", "@esbuild/linux-ia32": "0.25.6", "@esbuild/linux-loong64": "0.25.6", "@esbuild/linux-mips64el": "0.25.6", "@esbuild/linux-ppc64": "0.25.6", "@esbuild/linux-riscv64": "0.25.6", "@esbuild/linux-s390x": "0.25.6", "@esbuild/linux-x64": "0.25.6", "@esbuild/netbsd-arm64": "0.25.6", "@esbuild/netbsd-x64": "0.25.6", "@esbuild/openbsd-arm64": "0.25.6", "@esbuild/openbsd-x64": "0.25.6", "@esbuild/openharmony-arm64": "0.25.6", "@esbuild/sunos-x64": "0.25.6", "@esbuild/win32-arm64": "0.25.6", "@esbuild/win32-ia32": "0.25.6", "@esbuild/win32-x64": "0.25.6" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg=="],
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"esbuild": ["esbuild@0.25.8", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.8", "@esbuild/android-arm": "0.25.8", "@esbuild/android-arm64": "0.25.8", "@esbuild/android-x64": "0.25.8", "@esbuild/darwin-arm64": "0.25.8", "@esbuild/darwin-x64": "0.25.8", "@esbuild/freebsd-arm64": "0.25.8", "@esbuild/freebsd-x64": "0.25.8", "@esbuild/linux-arm": "0.25.8", "@esbuild/linux-arm64": "0.25.8", "@esbuild/linux-ia32": "0.25.8", "@esbuild/linux-loong64": "0.25.8", "@esbuild/linux-mips64el": "0.25.8", "@esbuild/linux-ppc64": "0.25.8", "@esbuild/linux-riscv64": "0.25.8", "@esbuild/linux-s390x": "0.25.8", "@esbuild/linux-x64": "0.25.8", "@esbuild/netbsd-arm64": "0.25.8", "@esbuild/netbsd-x64": "0.25.8", "@esbuild/openbsd-arm64": "0.25.8", "@esbuild/openbsd-x64": "0.25.8", "@esbuild/openharmony-arm64": "0.25.8", "@esbuild/sunos-x64": "0.25.8", "@esbuild/win32-arm64": "0.25.8", "@esbuild/win32-ia32": "0.25.8", "@esbuild/win32-x64": "0.25.8" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q=="],
|
||||
|
||||
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
|
||||
|
||||
@@ -527,6 +557,8 @@
|
||||
|
||||
"focus-trap-react": ["focus-trap-react@11.0.4", "", { "dependencies": { "focus-trap": "^7.6.5", "tabbable": "^6.2.0" }, "peerDependencies": { "@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" } }, "sha512-tC7jC/yqeAqhe4irNIzdyDf9XCtGSeECHiBSYJBO/vIN0asizbKZCt8TarB6/XqIceu42ajQ/U4lQJ9pZlWjrg=="],
|
||||
|
||||
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||
|
||||
"fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
|
||||
@@ -535,14 +567,30 @@
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
|
||||
@@ -557,16 +605,50 @@
|
||||
|
||||
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
|
||||
|
||||
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
||||
|
||||
"is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="],
|
||||
|
||||
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
|
||||
|
||||
"is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="],
|
||||
|
||||
"is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="],
|
||||
|
||||
"is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
|
||||
"is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
|
||||
|
||||
"is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
|
||||
|
||||
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
|
||||
|
||||
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
|
||||
|
||||
"is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
|
||||
|
||||
"is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
|
||||
|
||||
"is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="],
|
||||
|
||||
"is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="],
|
||||
|
||||
"is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
|
||||
|
||||
"is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="],
|
||||
|
||||
"is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
||||
|
||||
"isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
|
||||
@@ -579,7 +661,7 @@
|
||||
|
||||
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
|
||||
"jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="],
|
||||
|
||||
"jju": ["jju@1.4.0", "", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="],
|
||||
|
||||
@@ -631,7 +713,7 @@
|
||||
|
||||
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||
|
||||
"loupe": ["loupe@3.1.4", "", {}, "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg=="],
|
||||
"loupe": ["loupe@3.2.0", "", {}, "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw=="],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
@@ -643,6 +725,8 @@
|
||||
|
||||
"make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
|
||||
|
||||
"min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
|
||||
@@ -671,6 +755,14 @@
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"object-is": ["object-is@1.1.6", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" } }, "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q=="],
|
||||
|
||||
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
|
||||
|
||||
"object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
|
||||
|
||||
"open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
|
||||
|
||||
"p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="],
|
||||
@@ -709,6 +801,8 @@
|
||||
|
||||
"playwright-core": ["playwright-core@1.54.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA=="],
|
||||
|
||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
|
||||
@@ -745,6 +839,8 @@
|
||||
|
||||
"redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
|
||||
|
||||
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
||||
@@ -753,14 +849,28 @@
|
||||
|
||||
"rollup": ["rollup@4.45.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.45.1", "@rollup/rollup-android-arm64": "4.45.1", "@rollup/rollup-darwin-arm64": "4.45.1", "@rollup/rollup-darwin-x64": "4.45.1", "@rollup/rollup-freebsd-arm64": "4.45.1", "@rollup/rollup-freebsd-x64": "4.45.1", "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", "@rollup/rollup-linux-arm-musleabihf": "4.45.1", "@rollup/rollup-linux-arm64-gnu": "4.45.1", "@rollup/rollup-linux-arm64-musl": "4.45.1", "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-musl": "4.45.1", "@rollup/rollup-linux-s390x-gnu": "4.45.1", "@rollup/rollup-linux-x64-gnu": "4.45.1", "@rollup/rollup-linux-x64-musl": "4.45.1", "@rollup/rollup-win32-arm64-msvc": "4.45.1", "@rollup/rollup-win32-ia32-msvc": "4.45.1", "@rollup/rollup-win32-x64-msvc": "4.45.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw=="],
|
||||
|
||||
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
||||
|
||||
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
||||
|
||||
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
||||
|
||||
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
|
||||
|
||||
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
|
||||
|
||||
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
@@ -781,7 +891,9 @@
|
||||
|
||||
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
|
||||
|
||||
"storybook": ["storybook@9.0.17", "", { "dependencies": { "@storybook/global": "^5.0.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "better-opn": "^3.0.2", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", "esbuild-register": "^3.5.0", "recast": "^0.23.5", "semver": "^7.6.2", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./bin/index.cjs" }, "sha512-O+9jgJ+Trlq9VGD1uY4OBLKQWHHDKM/A/pA8vMW6PVehhGHNvpzcIC1bngr6mL5gGHZP2nBv+9XG8pTMcggMmg=="],
|
||||
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
||||
|
||||
"storybook": ["storybook@9.0.18", "", { "dependencies": { "@storybook/global": "^5.0.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "better-opn": "^3.0.2", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", "esbuild-register": "^3.5.0", "recast": "^0.23.5", "semver": "^7.6.2", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./bin/index.cjs" }, "sha512-ruxpEpizwoYQTt1hBOrWyp9trPYWD9Apt1TJ37rs1rzmNQWpSNGJDMg91JV4mUhBChzRvnid/oRBFFCWJz/dfw=="],
|
||||
|
||||
"string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="],
|
||||
|
||||
@@ -861,7 +973,7 @@
|
||||
|
||||
"use-isomorphic-layout-effect": ["use-isomorphic-layout-effect@1.2.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA=="],
|
||||
|
||||
"vite": ["vite@7.0.4", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "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" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA=="],
|
||||
"vite": ["vite@7.0.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "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" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg=="],
|
||||
|
||||
"vite-node": ["vite-node@3.2.4", "", { "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" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
|
||||
|
||||
@@ -875,6 +987,12 @@
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
||||
|
||||
"which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
|
||||
|
||||
"which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="],
|
||||
|
||||
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
@@ -911,11 +1029,11 @@
|
||||
|
||||
"@rushstack/terminal/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.4", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.5", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" }, "bundled": true }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.4", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.5", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.4", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" }, "bundled": true }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||
|
||||
@@ -923,8 +1041,6 @@
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@testing-library/jest-dom/aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
|
||||
|
||||
"@testing-library/jest-dom/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
|
||||
|
||||
"@testing-library/jest-dom/dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
|
||||
|
||||
@@ -5,13 +5,13 @@ import {
|
||||
type AccordionItemPropsPublic,
|
||||
} from "./components/AccordionItem";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
|
||||
export type AccordionProps = HTMLProps<"div"> & {
|
||||
expandedKeys: string[];
|
||||
type?: "multi" | "single";
|
||||
setExpandedKeys(keys: string[]): void;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
type AccordionType = React.FC<PropsWithChildren<AccordionProps>> & {
|
||||
Item: React.FC<PropsWithChildren<AccordionItemPropsPublic>>;
|
||||
@@ -23,6 +23,7 @@ const Accordion: AccordionType = ({
|
||||
setExpandedKeys,
|
||||
children,
|
||||
type = "multi",
|
||||
testId,
|
||||
...props
|
||||
}) => {
|
||||
const onChange = useCallback(
|
||||
@@ -54,6 +55,7 @@ const Accordion: AccordionType = ({
|
||||
return (
|
||||
<div
|
||||
className={cn("flex flex-col gap-y-2.5 items-start", className)}
|
||||
data-testid={testId}
|
||||
{...props}
|
||||
>
|
||||
{items}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import type { HTMLProps } from "../../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../../shared/types";
|
||||
import { cn } from "../../../shared/utils/cn";
|
||||
import { Icon, type IconProps } from "../../icon/Icon";
|
||||
import { Typography } from "../../typography/Typography";
|
||||
@@ -10,7 +10,7 @@ export type AccordionHeaderProps = Omit<
|
||||
> & {
|
||||
icon: IconProps["icon"];
|
||||
expanded: boolean;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const AccordionHeader = ({
|
||||
className,
|
||||
@@ -43,7 +43,8 @@ export const AccordionHeader = ({
|
||||
// hover modifier
|
||||
"data-[expanded=true]:hover:bg-light-neutral-900",
|
||||
// focus modifier
|
||||
"data-[expanded=false]:focus:bg-light-neutral-900"
|
||||
"data-[expanded=false]:focus:bg-light-neutral-900",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Icon icon={icon} className={cn(iconCss, "w-6 h-6")} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import type { HTMLProps } from "../../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../../shared/types";
|
||||
import { cn } from "../../../shared/utils/cn";
|
||||
import { type IconProps } from "../../icon/Icon";
|
||||
import { AccordionHeader } from "./AccordionHeader";
|
||||
@@ -11,10 +11,10 @@ export type AccordionItemProps = HTMLProps<"div"> & {
|
||||
value: string;
|
||||
label: React.ReactNode;
|
||||
onExpandedChange(value: boolean): void;
|
||||
};
|
||||
} & BaseProps;
|
||||
export type AccordionItemPropsPublic = Omit<
|
||||
AccordionItemProps,
|
||||
"expanded" | "onExpandedChange"
|
||||
"expanded" | "onExpandedChange" | "className" | "style" | "testId"
|
||||
>;
|
||||
|
||||
export const AccordionItem = ({
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import type { HTMLProps } from "../../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../../shared/types";
|
||||
import { cn } from "../../../shared/utils/cn";
|
||||
|
||||
export type AccordionPanelProps = Omit<HTMLProps<"div">, "aria-expanded"> & {
|
||||
expanded: boolean;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const AccordionPanel = ({
|
||||
className,
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import type { PropsWithChildren, ReactElement } from "react";
|
||||
import type { ComponentVariant, HTMLProps } from "../../shared/types";
|
||||
import {
|
||||
useEffect,
|
||||
useRef,
|
||||
type PropsWithChildren,
|
||||
type ReactElement,
|
||||
} from "react";
|
||||
import type {
|
||||
BaseProps,
|
||||
ComponentVariant,
|
||||
HTMLProps,
|
||||
} from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { invariant } from "../../shared/utils/invariant";
|
||||
import { buttonStyles } from "./utils";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import { buttonStyles, useAndApplyBoldTextWidth } from "./utils";
|
||||
import { cloneIcon } from "../../shared/utils/clone-icon";
|
||||
import "./index.css";
|
||||
|
||||
export type ButtonProps = Omit<HTMLProps<"button">, "aria-disabled"> & {
|
||||
size?: "small" | "large";
|
||||
variant?: ComponentVariant;
|
||||
start?: ReactElement<HTMLProps<"svg">>;
|
||||
end?: ReactElement<HTMLProps<"svg">>;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const Button = ({
|
||||
size = "small",
|
||||
@@ -20,39 +28,42 @@ export const Button = ({
|
||||
children,
|
||||
start,
|
||||
end,
|
||||
testId,
|
||||
...props
|
||||
}: PropsWithChildren<ButtonProps>) => {
|
||||
invariant(typeof children === "string", "Children must be string");
|
||||
const buttonClassNames = buttonStyles[variant];
|
||||
|
||||
const iconCss = "w-6 h-6";
|
||||
const hasIcons = start || end;
|
||||
const textRef = useAndApplyBoldTextWidth(children, "text-increase-size");
|
||||
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
aria-disabled={props.disabled ? "true" : "false"}
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
size === "small" ? "px-3 py-1.5 min-w-32" : "px-3 py-3 min-w-64",
|
||||
size === "small" ? "px-2 py-3 min-w-32" : "px-3 py-4 min-w-64",
|
||||
"flex flex-row items-center gap-x-8",
|
||||
hasIcons ? " justify-between" : " justify-center",
|
||||
"group enabled:cursor-pointer focus:outline-0",
|
||||
buttonClassNames.button
|
||||
buttonClassNames.button,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{cloneIcon(start, {
|
||||
className: cn(iconCss, buttonClassNames.icon),
|
||||
})}
|
||||
|
||||
<Typography.Text
|
||||
fontSize="l"
|
||||
<span
|
||||
ref={textRef}
|
||||
className={cn(
|
||||
"text-center font-size-l font-normal",
|
||||
buttonClassNames.text
|
||||
"tg-family-outfit tg-lg text-center font-normal leading-[100%]",
|
||||
buttonClassNames.text,
|
||||
!props.disabled && `button-bold-text`
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Typography.Text>
|
||||
</span>
|
||||
{cloneIcon(end, {
|
||||
className: cn(iconCss, buttonClassNames.icon),
|
||||
})}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
.group .button-bold-text::before,
|
||||
.group .button-bold-text::after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: var(--text-increase-size);
|
||||
transition: width 0.2s ease;
|
||||
transition: font-weight 0.2s ease;
|
||||
}
|
||||
|
||||
.group:hover .button-bold-text::before,
|
||||
.group:hover .button-bold-text::after,
|
||||
.group:focus .button-bold-text::before,
|
||||
.group:focus .button-bold-text::after {
|
||||
width: 0;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useEffect, useRef, type ReactNode } from "react";
|
||||
import type { ComponentVariant } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
|
||||
@@ -14,14 +15,22 @@ export const buttonStyles: Record<ComponentVariant, ButtonStyle> = {
|
||||
// hover modifier
|
||||
"enabled:hover:bg-grey-900 enabled:hover:ring-[1.5px]",
|
||||
// focus modifier
|
||||
"enabled:hover:bg-grey-900 enabled:focus:ring-[1.5px]",
|
||||
"enabled:hover:bg-grey-900 enabled:focus-visible:ring-[1.5px]",
|
||||
// active modifier
|
||||
"enabled:active:ring-1",
|
||||
// disabled modifier
|
||||
"disabled:opacity-50",
|
||||
]),
|
||||
icon: cn(["text-primary-500"]),
|
||||
text: cn(["text-primary-500"]),
|
||||
text: cn([
|
||||
"text-primary-500",
|
||||
// hover modifier
|
||||
"group-enabled:group-hover:font-semibold",
|
||||
// focus modifier
|
||||
"group-enabled:group-focus-visible:font-semibold",
|
||||
// active modifier
|
||||
"group-enabled:group-active:font-normal",
|
||||
]),
|
||||
},
|
||||
secondary: {
|
||||
button: cn([
|
||||
@@ -29,14 +38,22 @@ export const buttonStyles: Record<ComponentVariant, ButtonStyle> = {
|
||||
// hover modifier
|
||||
"enabled:hover:bg-light-neutral-900 enabled:hover:ring-[1.5px]",
|
||||
// focus modifier
|
||||
"enabled:focus:bg-light-neutral-900 enabled:focus:ring-[1.5px]",
|
||||
"enabled:focus-visible:bg-light-neutral-900 enabled:focus-visible:ring-[1.5px]",
|
||||
// active modifier
|
||||
"enabled:active:ring-1",
|
||||
// disabled modifier
|
||||
"disabled:opacity-50",
|
||||
]),
|
||||
icon: cn(["text-light-neutral-300"]),
|
||||
text: cn(["text-light-neutral-300"]),
|
||||
text: cn([
|
||||
"text-light-neutral-300",
|
||||
// hover modifier
|
||||
"group-enabled:group-hover:font-semibold",
|
||||
// focus modifier
|
||||
"group-enabled:group-focus-visible:font-semibold",
|
||||
// active modifier
|
||||
"group-enabled:group-active:font-normal",
|
||||
]),
|
||||
},
|
||||
tertiary: {
|
||||
button: cn([
|
||||
@@ -44,7 +61,7 @@ export const buttonStyles: Record<ComponentVariant, ButtonStyle> = {
|
||||
// hover modifier
|
||||
"enabled:hover:bg-grey-900",
|
||||
// focus modifier
|
||||
"enabled:focus:bg-grey-900",
|
||||
"enabled:focus-visible:bg-grey-900",
|
||||
// active modifier
|
||||
"enabled:active:bg-grey-970",
|
||||
// disabled modifier
|
||||
@@ -53,8 +70,38 @@ export const buttonStyles: Record<ComponentVariant, ButtonStyle> = {
|
||||
icon: cn(["text-primary-500"]),
|
||||
text: cn([
|
||||
"text-primary-500 underline",
|
||||
// hover modifier
|
||||
"group-enabled:group-hover:font-semibold",
|
||||
// focus modifier
|
||||
"group-enabled:group-focus-visible:font-semibold",
|
||||
// disabled modifier
|
||||
"group-disabled:no-underline",
|
||||
// active modifier
|
||||
"group-enabled:group-active:font-normal",
|
||||
]),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom hook that calculates and applies a CSS custom property (variable)
|
||||
* based on the length of a text node. Useful for adjusting spacing or layout
|
||||
* to account for changes in font weight, such as bold text rendering wider.
|
||||
*/
|
||||
const BOLD_TEXT_INCREASE = 0.15;
|
||||
export const useAndApplyBoldTextWidth = (
|
||||
textNode: ReactNode,
|
||||
varName: string
|
||||
) => {
|
||||
const textRef = useRef<HTMLSpanElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (textRef) {
|
||||
const charCount =
|
||||
typeof textNode === "string" ? (textNode as string).length : 0;
|
||||
const textIncrease = charCount * BOLD_TEXT_INCREASE;
|
||||
textRef.current!.style.setProperty(`--${varName}`, `${textIncrease}px`);
|
||||
}
|
||||
}, [textRef.current]);
|
||||
|
||||
return textRef;
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const CheckboxComponent = (props: CheckboxProps) => {
|
||||
const [checked, setChecked] = useState(false);
|
||||
const [checked, setChecked] = useState(props.checked);
|
||||
return (
|
||||
<Checkbox
|
||||
{...props}
|
||||
@@ -28,6 +28,7 @@ const CheckboxComponent = (props: CheckboxProps) => {
|
||||
|
||||
export const Enabled: Story = {
|
||||
args: {
|
||||
checked: false,
|
||||
label:
|
||||
"Lorem Ipsum is simply dummy text of the printing and typesetting industry",
|
||||
},
|
||||
@@ -37,6 +38,7 @@ export const Enabled: Story = {
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
disabled: true,
|
||||
checked: false,
|
||||
label:
|
||||
"Lorem Ipsum is simply dummy text of the printing and typesetting industry",
|
||||
},
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { useId } from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import { Icon } from "../icon/Icon";
|
||||
|
||||
export type CheckboxProps = HTMLProps<"input"> & {
|
||||
export type CheckboxProps = Omit<
|
||||
HTMLProps<"input">,
|
||||
"checked" | "defaultChecked"
|
||||
> & {
|
||||
label: React.ReactNode;
|
||||
labelClassName?: string;
|
||||
};
|
||||
checked: boolean;
|
||||
} & BaseProps;
|
||||
|
||||
export const Checkbox = ({
|
||||
className,
|
||||
@@ -17,6 +21,7 @@ export const Checkbox = ({
|
||||
disabled,
|
||||
checked,
|
||||
onChange,
|
||||
testId,
|
||||
...props
|
||||
}: CheckboxProps) => {
|
||||
const generatedId = useId();
|
||||
@@ -26,8 +31,10 @@ export const Checkbox = ({
|
||||
htmlFor={id}
|
||||
className={cn(
|
||||
"flex items-center gap-2 cursor-pointer",
|
||||
disabled && "cursor-not-allowed"
|
||||
disabled && "cursor-not-allowed",
|
||||
className
|
||||
)}
|
||||
data-testid={testId}
|
||||
>
|
||||
<input
|
||||
id={id}
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
import { type PropsWithChildren } from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import { chipStyles, type ChipColor, type ChipVariant } from "./utils";
|
||||
import { invariant } from "../../shared/utils/invariant";
|
||||
|
||||
export type ChipProps = Omit<HTMLProps<"div">, "label"> & {
|
||||
color?: ChipColor;
|
||||
variant?: ChipVariant;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const Chip = ({
|
||||
className,
|
||||
color = "gray",
|
||||
variant = "pill",
|
||||
children,
|
||||
testId,
|
||||
...props
|
||||
}: PropsWithChildren<ChipProps>) => {
|
||||
invariant(typeof children === "string", "Children must be string");
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
"flex flex-row items-center px-1.5 py-1",
|
||||
variant === "pill" ? "rounded-full" : "rounded-lg",
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import {
|
||||
useEffect,
|
||||
useId,
|
||||
useRef,
|
||||
useState,
|
||||
type PropsWithChildren,
|
||||
} from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import { useId, type PropsWithChildren } from "react";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { Icon } from "../icon/Icon";
|
||||
import { createPortal } from "react-dom";
|
||||
import {
|
||||
FloatingOverlay,
|
||||
FloatingPortal,
|
||||
@@ -24,13 +17,14 @@ import { FocusTrap } from "focus-trap-react";
|
||||
export type DialogProps = HTMLProps<"div"> & {
|
||||
open: boolean;
|
||||
onOpenChange(value: boolean): void;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const Dialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
className,
|
||||
children,
|
||||
testId,
|
||||
}: PropsWithChildren<DialogProps>) => {
|
||||
const id = useId();
|
||||
|
||||
@@ -80,6 +74,7 @@ export const Dialog = ({
|
||||
aria-describedby={`${id}-description`}
|
||||
{...getFloatingProps()}
|
||||
style={styles}
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
"rounded-4xl border-1 border-light-neutral-500 outline-none",
|
||||
"transition-all will-change-transform",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
|
||||
export type DividerProps = Omit<
|
||||
@@ -6,11 +6,12 @@ export type DividerProps = Omit<
|
||||
"role" | "aria-orientation"
|
||||
> & {
|
||||
type?: "horizontal" | "vertical";
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const Divider = ({
|
||||
type = "horizontal",
|
||||
className,
|
||||
testId,
|
||||
...props
|
||||
}: DividerProps) => {
|
||||
return (
|
||||
@@ -23,6 +24,7 @@ export const Divider = ({
|
||||
)}
|
||||
role="separator"
|
||||
aria-orientation={type}
|
||||
data-testid={testId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
type ReactElement,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import { cloneIcon } from "../../shared/utils/clone-icon";
|
||||
@@ -19,7 +19,7 @@ export type InputProps = Omit<
|
||||
end?: ReactElement<HTMLProps<"svg">>;
|
||||
error?: string;
|
||||
hint?: string;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const Input = ({
|
||||
className,
|
||||
@@ -34,6 +34,7 @@ export const Input = ({
|
||||
type,
|
||||
hint,
|
||||
readOnly,
|
||||
testId,
|
||||
...props
|
||||
}: InputProps) => {
|
||||
const generatedId = useId();
|
||||
@@ -45,65 +46,64 @@ export const Input = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label
|
||||
htmlFor={id}
|
||||
<label
|
||||
htmlFor={id}
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
"flex flex-col gap-y-2",
|
||||
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
|
||||
)}
|
||||
>
|
||||
<Typography.Text fontSize="s" className="text-light-neutral-200">
|
||||
{label}
|
||||
</Typography.Text>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col gap-y-2",
|
||||
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
|
||||
"flex flex-row items-center gap-x-2.5",
|
||||
"py-4.25 px-4",
|
||||
"border-light-neutral-500 border-1 rounded-2xl",
|
||||
// base
|
||||
"bg-light-neutral-950",
|
||||
// hover modifier
|
||||
"hover:bg-light-neutral-900",
|
||||
// focus modifier
|
||||
"focus-within:bg-light-neutral-900",
|
||||
// error state
|
||||
error && " border-red-400 bg-light-neutral-970",
|
||||
readOnly &&
|
||||
"bg-light-neutral-985 border-none hover:bg-light-neutral-985 cursor-auto",
|
||||
// disabled modifier
|
||||
disabled && "hover:bg-light-neutral-950"
|
||||
)}
|
||||
>
|
||||
<Typography.Text fontSize="s" className="text-light-neutral-200">
|
||||
{label}
|
||||
</Typography.Text>
|
||||
<div
|
||||
{cloneIcon(start, {
|
||||
className: iconCss,
|
||||
})}
|
||||
<input
|
||||
id={id}
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
aria-invalid={error ? "true" : "false"}
|
||||
readOnly={readOnly}
|
||||
className={cn(
|
||||
"flex flex-row items-center gap-x-2.5",
|
||||
"py-4.25 px-4",
|
||||
"border-light-neutral-500 border-1 rounded-2xl",
|
||||
// base
|
||||
"bg-light-neutral-950",
|
||||
// hover modifier
|
||||
"hover:bg-light-neutral-900",
|
||||
// focus modifier
|
||||
"focus-within:bg-light-neutral-900",
|
||||
// error state
|
||||
error && " border-red-400 bg-light-neutral-970",
|
||||
readOnly &&
|
||||
"bg-light-neutral-985 border-none hover:bg-light-neutral-985 cursor-auto",
|
||||
// disabled modifier
|
||||
disabled && "hover:bg-light-neutral-950"
|
||||
"flex-1 outline-none caret-primary-500 text-white",
|
||||
"placeholder:text-light-neutral-300",
|
||||
error && "text-red-400"
|
||||
)}
|
||||
>
|
||||
{cloneIcon(start, {
|
||||
className: iconCss,
|
||||
})}
|
||||
<input
|
||||
id={id}
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
aria-invalid={error ? "true" : "false"}
|
||||
readOnly={readOnly}
|
||||
className={cn(
|
||||
"flex-1 outline-none caret-primary-500 text-white",
|
||||
"placeholder:text-light-neutral-300",
|
||||
error && "text-red-400"
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
{cloneIcon(end, {
|
||||
className: iconCss,
|
||||
})}
|
||||
</div>
|
||||
<Typography.Text
|
||||
fontSize="xs"
|
||||
className={cn("text-light-neutral-600 ml-4", error && "text-red-400")}
|
||||
>
|
||||
{error ?? hint}
|
||||
</Typography.Text>
|
||||
</label>
|
||||
</div>
|
||||
{...props}
|
||||
/>
|
||||
{cloneIcon(end, {
|
||||
className: iconCss,
|
||||
})}
|
||||
</div>
|
||||
<Typography.Text
|
||||
fontSize="xs"
|
||||
className={cn("text-light-neutral-600 ml-4", error && "text-red-400")}
|
||||
>
|
||||
{error ?? hint}
|
||||
</Typography.Text>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ const InteractiveChipComponent = (props: InteractiveChipProps) => {
|
||||
|
||||
export const Elevated: Story = {
|
||||
args: {
|
||||
type: "elevated",
|
||||
chipType: "elevated",
|
||||
disabled: false,
|
||||
},
|
||||
render: InteractiveChipComponent,
|
||||
@@ -37,7 +37,7 @@ export const Elevated: Story = {
|
||||
|
||||
export const Filled: Story = {
|
||||
args: {
|
||||
type: "filled",
|
||||
chipType: "filled",
|
||||
disabled: false,
|
||||
},
|
||||
render: InteractiveChipComponent,
|
||||
|
||||
@@ -1,84 +1,67 @@
|
||||
import {
|
||||
cloneElement,
|
||||
isValidElement,
|
||||
type PropsWithChildren,
|
||||
type ReactElement,
|
||||
} from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import { type PropsWithChildren, type ReactElement } from "react";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { Typography } from "../typography/Typography";
|
||||
|
||||
import { invariant } from "../../shared/utils/invariant";
|
||||
import { interactiveChipStyles, type InteractiveChipType } from "./utils";
|
||||
import { cloneIcon } from "../../shared/utils/clone-icon";
|
||||
import "./index.css";
|
||||
import {
|
||||
buttonStyles,
|
||||
useAndApplyBoldTextWidth,
|
||||
type InteractiveChipType,
|
||||
} from "./utils";
|
||||
|
||||
export type InteractiveChipProps = Omit<
|
||||
HTMLProps<"div">,
|
||||
"label" | "aria-disabled" | "tabIndex"
|
||||
HTMLProps<"button">,
|
||||
"aria-disabled"
|
||||
> & {
|
||||
chipType?: InteractiveChipType;
|
||||
start?: ReactElement<HTMLProps<"svg">>;
|
||||
onStartClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
end?: ReactElement<HTMLProps<"svg">>;
|
||||
onEndClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
type?: InteractiveChipType;
|
||||
disabled?: boolean;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const InteractiveChip = ({
|
||||
chipType = "elevated",
|
||||
className,
|
||||
children,
|
||||
start,
|
||||
end,
|
||||
type = "elevated",
|
||||
children,
|
||||
disabled = false,
|
||||
onStartClick,
|
||||
onEndClick,
|
||||
testId,
|
||||
...props
|
||||
}: PropsWithChildren<InteractiveChipProps>) => {
|
||||
invariant(typeof children === "string", "Children must be string");
|
||||
|
||||
const iconCss = cn("w-4 h-4 text-inherit");
|
||||
const buttonCss = cn(disabled ? "cursor-not-allowed" : "cursor-pointer");
|
||||
|
||||
const interactiveChipClassName = interactiveChipStyles[type];
|
||||
const buttonClassNames = buttonStyles[chipType];
|
||||
const iconCss = "w-6 h-6";
|
||||
const hasIcons = start || end;
|
||||
const textRef = useAndApplyBoldTextWidth(children, "text-increase-size");
|
||||
|
||||
return (
|
||||
<div
|
||||
<button
|
||||
{...props}
|
||||
data-disabled={disabled ? "true" : "false"}
|
||||
aria-disabled={disabled ? "true" : "false"}
|
||||
data-testid={testId}
|
||||
aria-disabled={props.disabled ? "true" : "false"}
|
||||
className={cn(
|
||||
"flex flex-row items-center p-1 gap-x-1 rounded-lg",
|
||||
"active:data-[disabled=false]:scale-90 transition",
|
||||
interactiveChipClassName,
|
||||
className
|
||||
"px-1.5 py-1 min-w-32",
|
||||
"flex flex-row items-center gap-x-2",
|
||||
hasIcons ? " justify-between" : " justify-center",
|
||||
"group enabled:cursor-pointer focus:outline-0",
|
||||
buttonClassNames.button
|
||||
)}
|
||||
>
|
||||
{start && isValidElement(start) ? (
|
||||
<button
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
onClick={onStartClick}
|
||||
className={cn(buttonCss)}
|
||||
>
|
||||
{cloneElement(start, {
|
||||
className: iconCss,
|
||||
})}
|
||||
</button>
|
||||
) : null}
|
||||
<Typography.Text fontSize="xs" className="text-inherit">
|
||||
{children}
|
||||
</Typography.Text>
|
||||
{cloneIcon(start, {
|
||||
className: cn(iconCss, buttonClassNames.icon),
|
||||
})}
|
||||
|
||||
{end && isValidElement(end) ? (
|
||||
<button
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
onClick={onEndClick}
|
||||
className={cn(buttonCss)}
|
||||
>
|
||||
{cloneElement(end, {
|
||||
className: iconCss,
|
||||
})}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
<span
|
||||
ref={textRef}
|
||||
className={cn(
|
||||
"tg-family-outfit tg-xs text-center font-normal line-1",
|
||||
buttonClassNames.text,
|
||||
!props.disabled && `button-bold-text`
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
{cloneIcon(end, {
|
||||
className: cn(iconCss, buttonClassNames.icon),
|
||||
})}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
.group .button-bold-text::before,
|
||||
.group .button-bold-text::after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: var(--text-increase-size);
|
||||
transition: width 0.2s ease;
|
||||
transition: font-weight 0.2s ease;
|
||||
}
|
||||
|
||||
.group:hover .button-bold-text::before,
|
||||
.group:hover .button-bold-text::after,
|
||||
.group:focus .button-bold-text::before,
|
||||
.group:focus .button-bold-text::after {
|
||||
width: 0;
|
||||
}
|
||||
@@ -1,30 +1,98 @@
|
||||
import { useEffect, useRef, type ReactNode } from "react";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
|
||||
export type InteractiveChipType = "elevated" | "filled";
|
||||
|
||||
export const interactiveChipStyles: Record<InteractiveChipType, string> = {
|
||||
elevated: cn([
|
||||
// base
|
||||
"data-[disabled=false]:border-1 border-light-neutral-400 text-light-neutral-400 bg-light-neutral-950 font-normal",
|
||||
// hover modifier
|
||||
"hover:border-light-neutral-100 hover:data-[disabled=false]:text-light-neutral-100 hover:data-[disabled=false]:font-semibold hover:data-[disabled=false]:bg-light-neutral-800",
|
||||
// focus modifier
|
||||
"focus:border-light-neutral-100 focus:text-light-neutral-100 focus:font-semibold focus:bg-light-neutral-800",
|
||||
// active modifier
|
||||
"active:data-[disabled=false]:border-primary-500 active:data-[disabled=false]:text-primary-500",
|
||||
// disabled modifier
|
||||
"data-[disabled=true]:opacity-50 data-[disabled=true]:bg-light-neutral-900",
|
||||
]),
|
||||
filled: cn([
|
||||
// base
|
||||
"text-grey-985 bg-light-neutral-600 font-normal",
|
||||
// hover modifier
|
||||
"hover:data-[disabled=false]:font-semibold hover:data-[disabled=false]:bg-light-neutral-300",
|
||||
// focus modifier
|
||||
"focus:data-[disabled=false]:font-semibold focus:data-[disabled=false]:bg-light-neutral-300",
|
||||
// active modifier
|
||||
"active:data-[disabled=false]:bg-primary-100",
|
||||
// disabled modifier
|
||||
"data-[disabled=true]:opacity-40 data-[disabled=true]:bg-light-neutral-400",
|
||||
]),
|
||||
type ButtonStyle = {
|
||||
button: string;
|
||||
icon: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
export const buttonStyles: Record<InteractiveChipType, ButtonStyle> = {
|
||||
elevated: {
|
||||
button: cn([
|
||||
"ring-1 ring-solid ring-light-neutral-400 rounded-xl bg-light-neutral-950 transition-scale duration-200",
|
||||
// hover modifier
|
||||
"enabled:hover:bg-light-neutral-800 enabled:hover:ring-light-neutral-15",
|
||||
// focus modifier
|
||||
"enabled:focus:bg-light-neutral-800 enabled:focus:ring-light-neutral-15",
|
||||
// active modifier
|
||||
"enabled:active:ring-primary-500 enabled:active:scale-90 enabled:active:bg-light-neutral-900",
|
||||
// disabled modifier
|
||||
"disabled:opacity-40 disabled:bg-light-neutral-900 disabled:ring-0 disabled:font-medium",
|
||||
]),
|
||||
icon: cn([
|
||||
"text-light-neutral-400",
|
||||
// hover modifier
|
||||
"group-enabled:group-hover:font-semibold group-enabled:group-hover:text-light-neutral-15",
|
||||
// focus modifier
|
||||
"group-enabled:group-focus:font-semibold group-enabled:group-focus:text-light-neutral-15",
|
||||
// active modifier
|
||||
"group-enabled:group-active:text-primary-500",
|
||||
]),
|
||||
text: cn([
|
||||
"text-light-neutral-400",
|
||||
// hover modifier
|
||||
"group-enabled:group-hover:font-semibold group-enabled:group-hover:text-light-neutral-15",
|
||||
// focus modifier
|
||||
"group-enabled:group-focus:font-semibold group-enabled:group-focus:text-light-neutral-15",
|
||||
// active modifier
|
||||
"group-enabled:group-active:text-primary-500",
|
||||
]),
|
||||
},
|
||||
filled: {
|
||||
button: cn([
|
||||
"rounded-xl bg-light-neutral-600 transition-scale duration-200",
|
||||
// hover modifier
|
||||
"enabled:hover:bg-light-neutral-300",
|
||||
// focus modifier
|
||||
"enabled:focus:bg-light-neutral-300",
|
||||
// active modifier
|
||||
"enabled:active:scale-90 enabled:active:bg-primary-100",
|
||||
// disabled modifier
|
||||
"disabled:opacity-40 disabled:bg-light-neutral-400 disabled:font-medium",
|
||||
]),
|
||||
icon: cn([
|
||||
"text-light-neutral-985",
|
||||
// hover modifier
|
||||
"group-enabled:group-hover:font-semibold",
|
||||
// focus modifier
|
||||
"group-enabled:group-focus:font-semibold",
|
||||
// active modifier
|
||||
"group-enabled:group-active:text-light-neutral-970",
|
||||
]),
|
||||
text: cn([
|
||||
"text-light-neutral-985",
|
||||
// hover modifier
|
||||
"group-enabled:group-hover:font-semibold",
|
||||
// focus modifier
|
||||
"group-enabled:group-focus:font-semibold",
|
||||
// active modifier
|
||||
"group-enabled:group-active:text-light-neutral-970",
|
||||
]),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom hook that calculates and applies a CSS custom property (variable)
|
||||
* based on the length of a text node. Useful for adjusting spacing or layout
|
||||
* to account for changes in font weight, such as bold text rendering wider.
|
||||
*/
|
||||
const BOLD_TEXT_INCREASE = 0.15;
|
||||
export const useAndApplyBoldTextWidth = (
|
||||
textNode: ReactNode,
|
||||
varName: string
|
||||
) => {
|
||||
const textRef = useRef<HTMLSpanElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (textRef) {
|
||||
const charCount =
|
||||
typeof textNode === "string" ? (textNode as string).length : 0;
|
||||
const textIncrease = charCount * BOLD_TEXT_INCREASE;
|
||||
textRef.current!.style.setProperty(`--${varName}`, `${textIncrease}px`);
|
||||
}
|
||||
}, [textRef.current]);
|
||||
|
||||
return textRef;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useId } from "react";
|
||||
import type { HTMLProps, IOption } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps, IOption } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { RadioOption } from "./RadioOption";
|
||||
|
||||
@@ -11,7 +11,7 @@ export type RadioGroupProps<T extends string> = Omit<
|
||||
value: T;
|
||||
onChange: (option: IOption<T>) => void;
|
||||
labelClassName?: string;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const RadioGroup = <T extends string>({
|
||||
value,
|
||||
@@ -21,13 +21,17 @@ export const RadioGroup = <T extends string>({
|
||||
labelClassName,
|
||||
disabled,
|
||||
id: propId,
|
||||
testId,
|
||||
...props
|
||||
}: RadioGroupProps<T>) => {
|
||||
const generatedId = useId();
|
||||
const id = propId ?? generatedId;
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-y-1", className)}>
|
||||
<div
|
||||
data-testid={testId}
|
||||
className={cn("flex flex-col gap-y-1", className)}
|
||||
>
|
||||
{options.map((o) => (
|
||||
<RadioOption
|
||||
{...props}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useId } from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
|
||||
@@ -7,7 +7,7 @@ type RadioOptionProps = Omit<HTMLProps<"input">, "id" | "checked"> & {
|
||||
label: React.ReactNode;
|
||||
labelClassName?: string;
|
||||
id: string;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const RadioOption = ({
|
||||
className,
|
||||
@@ -17,6 +17,7 @@ export const RadioOption = ({
|
||||
id: propId,
|
||||
disabled,
|
||||
onChange,
|
||||
testId,
|
||||
...props
|
||||
}: RadioOptionProps) => {
|
||||
const generatedId = useId();
|
||||
@@ -25,6 +26,7 @@ export const RadioOption = ({
|
||||
return (
|
||||
<label
|
||||
htmlFor={id}
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
"flex items-center gap-x-4",
|
||||
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
|
||||
export type ScrollableMode = "auto" | "scroll";
|
||||
@@ -8,7 +8,7 @@ export type ScrollableType = "horizontal" | "vertical";
|
||||
export type ScrollableProps = HTMLProps<"div"> & {
|
||||
mode?: ScrollableMode;
|
||||
type?: ScrollableType;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
const scrollableStyles: Record<
|
||||
ScrollableType,
|
||||
@@ -30,11 +30,13 @@ export const Scrollable = ({
|
||||
tabIndex,
|
||||
mode = "auto",
|
||||
type = "vertical",
|
||||
testId,
|
||||
...props
|
||||
}: PropsWithChildren<ScrollableProps>) => {
|
||||
const style = scrollableStyles[type][mode];
|
||||
return (
|
||||
<div
|
||||
data-testid={testId}
|
||||
tabIndex={tabIndex ?? 0}
|
||||
{...props}
|
||||
className={cn(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useId, useMemo, useState } from "react";
|
||||
import type { HTMLProps, IOption } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps, IOption } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import ReactSelect, { createFilter } from "react-select";
|
||||
import { Typography } from "../typography/Typography";
|
||||
@@ -16,7 +16,7 @@ export type SelectProps<T> = Omit<HTMLProps<"input">, "value" | "onChange"> & {
|
||||
options: IOption<T>[];
|
||||
noOptionsText?: string;
|
||||
onChange(value: IOption<T> | null): void;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const Select = <T extends string>(props: SelectProps<T>) => {
|
||||
const {
|
||||
@@ -32,6 +32,8 @@ export const Select = <T extends string>(props: SelectProps<T>) => {
|
||||
onChange,
|
||||
readOnly,
|
||||
noOptionsText,
|
||||
className,
|
||||
testId,
|
||||
} = props;
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const generatedId = useId();
|
||||
@@ -50,10 +52,12 @@ export const Select = <T extends string>(props: SelectProps<T>) => {
|
||||
|
||||
return (
|
||||
<label
|
||||
data-testid={testId}
|
||||
htmlFor={id}
|
||||
className={cn(
|
||||
"flex flex-col gap-y-2",
|
||||
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
|
||||
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Typography.Text fontSize="s" className="text-light-neutral-200">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import "./index.css";
|
||||
|
||||
@@ -15,7 +15,11 @@ export type IndeterminateSpinnerProps = BaseSpinnerProps & {
|
||||
value?: never;
|
||||
};
|
||||
|
||||
export type SpinnerProps = DeterminateSpinnerProps | IndeterminateSpinnerProps;
|
||||
export type SpinnerProps = (
|
||||
| DeterminateSpinnerProps
|
||||
| IndeterminateSpinnerProps
|
||||
) &
|
||||
BaseProps;
|
||||
|
||||
const SIZE = 48;
|
||||
const STROKE_WIDTH = 6;
|
||||
@@ -26,6 +30,7 @@ export const Spinner = ({
|
||||
value = 10,
|
||||
determinate = false,
|
||||
className,
|
||||
testId,
|
||||
...props
|
||||
}: SpinnerProps) => {
|
||||
const offset = useMemo(
|
||||
@@ -34,7 +39,13 @@ export const Spinner = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<svg width={SIZE} height={SIZE} className={className} {...props}>
|
||||
<svg
|
||||
data-testid={testId}
|
||||
width={SIZE}
|
||||
height={SIZE}
|
||||
className={className}
|
||||
{...props}
|
||||
>
|
||||
<circle
|
||||
cx={SIZE / 2}
|
||||
cy={SIZE / 2}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
type PropsWithChildren,
|
||||
type ReactElement,
|
||||
} from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import React from "react";
|
||||
import {
|
||||
@@ -16,13 +16,13 @@ import { useElementOverflow } from "./hooks/use-element-overflow";
|
||||
import { useElementScroll } from "./hooks/use-element-scroll";
|
||||
import { TabScroller } from "./components/TabScroller";
|
||||
|
||||
export type TabsProps = HTMLProps<"div">;
|
||||
export type TabsProps = HTMLProps<"div"> & BaseProps;
|
||||
|
||||
type TabsType = React.FC<PropsWithChildren<TabsProps>> & {
|
||||
Item: React.FC<PropsWithChildren<TabItemPropsPublic>>;
|
||||
};
|
||||
|
||||
const Tabs: TabsType = ({ children, ...props }) => {
|
||||
const Tabs: TabsType = ({ children, className, testId, ...props }) => {
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const tabListRef = useRef<HTMLDivElement>(null);
|
||||
@@ -55,7 +55,7 @@ const Tabs: TabsType = ({ children, ...props }) => {
|
||||
}) ?? [];
|
||||
|
||||
return (
|
||||
<div className={cn("w-full")}>
|
||||
<div data-testid={testId} className={cn("w-full", className)}>
|
||||
<div className={cn("flex flex-row items-stretch")} ref={containerRef}>
|
||||
{canScrollLeft && isOverflowing && (
|
||||
<TabScroller onScroll={scrollLeft} position="left" />
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Button } from "../button/Button";
|
||||
import { ToastManager } from "./ToastManager";
|
||||
import { toasterMessages } from "./Toast";
|
||||
import { ToastManager } from "./ToastManager";
|
||||
import { Typography } from "../typography/Typography";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Toast",
|
||||
@@ -14,13 +15,46 @@ const meta = {
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type ToastType = keyof typeof toasterMessages;
|
||||
|
||||
const toastComponents: Record<ToastType, (text?: string) => void> = {
|
||||
error: toasterMessages.error,
|
||||
success: toasterMessages.success,
|
||||
info: toasterMessages.info,
|
||||
warning: toasterMessages.warning,
|
||||
custom: (text) => toasterMessages.custom(() => <div>{text}</div>),
|
||||
};
|
||||
|
||||
const ToastComponent = () => {
|
||||
return (
|
||||
<ToastManager>
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<Button onClick={() => toastComponents["error"]("Lorem Ipsum")}>
|
||||
Show error toast
|
||||
</Button>
|
||||
<Button onClick={() => toastComponents["info"]("Lorem Ipsum")}>
|
||||
Show info toast
|
||||
</Button>
|
||||
<Button onClick={() => toastComponents["success"]("Lorem Ipsum")}>
|
||||
Show success toast
|
||||
</Button>
|
||||
<Button onClick={() => toastComponents["warning"]("Lorem Ipsum")}>
|
||||
Show warning toast
|
||||
</Button>
|
||||
<Button onClick={() => toastComponents["custom"]("Lorem Ipsum")}>
|
||||
Show custom toast
|
||||
</Button>
|
||||
</div>
|
||||
</ToastManager>
|
||||
);
|
||||
};
|
||||
const CustomToastComponent = () => {
|
||||
const notify = () => {
|
||||
toasterMessages.error("Lorem Ipsum");
|
||||
toasterMessages.success("Lorem Ipsum");
|
||||
toasterMessages.info("Lorem Ipsum");
|
||||
toasterMessages.warning("Lorem Ipsum");
|
||||
toasterMessages.custom((props) => (
|
||||
<Typography.Text fontSize="xs" className="text-white">
|
||||
Custom toast !
|
||||
</Typography.Text>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -34,5 +68,9 @@ const ToastComponent = () => {
|
||||
|
||||
export const Main: Story = {
|
||||
args: {},
|
||||
render: ToastComponent,
|
||||
render: () => <ToastComponent />,
|
||||
};
|
||||
export const Custom: Story = {
|
||||
args: {},
|
||||
render: CustomToastComponent,
|
||||
};
|
||||
|
||||
@@ -1,16 +1,40 @@
|
||||
import { toast as sonnerToast } from "sonner";
|
||||
import { toast as sonnerToast, type ExternalToast } from "sonner";
|
||||
import { Icon, type IconProps } from "../icon/Icon";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import { toastStyles } from "./utils";
|
||||
import type { JSX } from "react";
|
||||
import { invariant } from "../../shared/utils/invariant";
|
||||
import type { BaseProps } from "../../shared/types";
|
||||
|
||||
type IBaseToastProps = {
|
||||
id: string | number;
|
||||
type RenderContentProps = {
|
||||
onDismiss: () => void;
|
||||
};
|
||||
|
||||
type WithRenderContent = {
|
||||
renderContent: (props: RenderContentProps) => JSX.Element;
|
||||
text?: never;
|
||||
icon?: never;
|
||||
};
|
||||
|
||||
type WithTextAndIcon = {
|
||||
text: string;
|
||||
icon: IconProps["icon"];
|
||||
iconClassName: string;
|
||||
renderContent?: never;
|
||||
};
|
||||
|
||||
type IBaseToastProps = (WithRenderContent | WithTextAndIcon) & {
|
||||
id: string | number;
|
||||
};
|
||||
const BaseToast = (props: IBaseToastProps) => {
|
||||
invariant(
|
||||
!!props.renderContent || !!props.text,
|
||||
"Either define renderContent or text. Both cannot be defined."
|
||||
);
|
||||
|
||||
const onDismiss = () => sonnerToast.dismiss(props.id);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -20,66 +44,90 @@ const BaseToast = (props: IBaseToastProps) => {
|
||||
"flex flex-row items-center justify-between gap-x-4"
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
icon={props.icon}
|
||||
className={cn("w-6 h-6 flex-shrink-0", props.iconClassName)}
|
||||
/>
|
||||
<Typography.Text fontSize="xs" className="text-white">
|
||||
{props.text}
|
||||
</Typography.Text>
|
||||
<button
|
||||
onClick={() => sonnerToast.dismiss(props.id)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Icon icon="X" className={cn("w-6 h-6 flex-shrink-0 text-white")} />
|
||||
</button>
|
||||
{props.renderContent ? (
|
||||
props.renderContent({ onDismiss })
|
||||
) : (
|
||||
<>
|
||||
<Icon
|
||||
icon={props.icon}
|
||||
className={cn("w-6 h-6 flex-shrink-0", props.iconClassName)}
|
||||
/>
|
||||
<Typography.Text fontSize="xs" className="text-white">
|
||||
{props.text}
|
||||
</Typography.Text>
|
||||
<button onClick={onDismiss} className="cursor-pointer">
|
||||
<Icon icon="X" className={cn("w-6 h-6 flex-shrink-0 text-white")} />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const toasterMessages = {
|
||||
error: (text?: string) => {
|
||||
error: (text?: string, props?: ExternalToast) => {
|
||||
const styles = toastStyles["error"];
|
||||
sonnerToast.custom((id) => (
|
||||
<BaseToast
|
||||
id={id}
|
||||
icon={styles.icon}
|
||||
iconClassName={cn(styles.iconColor)}
|
||||
text={text!}
|
||||
/>
|
||||
));
|
||||
sonnerToast.custom(
|
||||
(id) => (
|
||||
<BaseToast
|
||||
id={id}
|
||||
icon={styles.icon}
|
||||
iconClassName={cn(styles.iconColor)}
|
||||
text={text!}
|
||||
/>
|
||||
),
|
||||
props
|
||||
);
|
||||
},
|
||||
success: (text?: string) => {
|
||||
success: (text?: string, props?: ExternalToast) => {
|
||||
const styles = toastStyles["success"];
|
||||
sonnerToast.custom((id) => (
|
||||
<BaseToast
|
||||
id={id}
|
||||
icon={styles.icon}
|
||||
iconClassName={cn(styles.iconColor)}
|
||||
text={text!}
|
||||
/>
|
||||
));
|
||||
sonnerToast.custom(
|
||||
(id) => (
|
||||
<BaseToast
|
||||
id={id}
|
||||
icon={styles.icon}
|
||||
iconClassName={cn(styles.iconColor)}
|
||||
text={text!}
|
||||
/>
|
||||
),
|
||||
props
|
||||
);
|
||||
},
|
||||
info: (text?: string) => {
|
||||
info: (text?: string, props?: ExternalToast) => {
|
||||
const styles = toastStyles["info"];
|
||||
sonnerToast.custom((id) => (
|
||||
<BaseToast
|
||||
id={id}
|
||||
icon={styles.icon}
|
||||
iconClassName={cn(styles.iconColor)}
|
||||
text={text!}
|
||||
/>
|
||||
));
|
||||
sonnerToast.custom(
|
||||
(id) => (
|
||||
<BaseToast
|
||||
id={id}
|
||||
icon={styles.icon}
|
||||
iconClassName={cn(styles.iconColor)}
|
||||
text={text!}
|
||||
/>
|
||||
),
|
||||
props
|
||||
);
|
||||
},
|
||||
warning: (text?: string) => {
|
||||
warning: (text?: string, props?: ExternalToast) => {
|
||||
const styles = toastStyles["warning"];
|
||||
sonnerToast.custom((id) => (
|
||||
<BaseToast
|
||||
id={id}
|
||||
icon={styles.icon}
|
||||
iconClassName={cn(styles.iconColor)}
|
||||
text={text!}
|
||||
/>
|
||||
));
|
||||
sonnerToast.custom(
|
||||
(id) => (
|
||||
<BaseToast
|
||||
id={id}
|
||||
icon={styles.icon}
|
||||
iconClassName={cn(styles.iconColor)}
|
||||
text={text!}
|
||||
/>
|
||||
),
|
||||
props
|
||||
);
|
||||
},
|
||||
custom: (
|
||||
renderContent: WithRenderContent["renderContent"],
|
||||
props?: ExternalToast
|
||||
) => {
|
||||
sonnerToast.custom(
|
||||
(id) => <BaseToast id={id} renderContent={renderContent} />,
|
||||
props
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useId } from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import { invariant } from "../../shared/utils/invariant";
|
||||
@@ -11,7 +11,8 @@ type ToggleTextProps =
|
||||
export type ToggleProps = HTMLProps<"input"> & {
|
||||
label?: React.ReactNode;
|
||||
labelClassName?: string;
|
||||
} & ToggleTextProps;
|
||||
} & ToggleTextProps &
|
||||
BaseProps;
|
||||
|
||||
export const Toggle = ({
|
||||
className,
|
||||
@@ -23,6 +24,7 @@ export const Toggle = ({
|
||||
onChange,
|
||||
onText,
|
||||
offText,
|
||||
testId,
|
||||
...props
|
||||
}: ToggleProps) => {
|
||||
invariant(
|
||||
@@ -49,6 +51,7 @@ export const Toggle = ({
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
className="sr-only peer"
|
||||
data-testid={testId}
|
||||
{...props}
|
||||
/>
|
||||
<div
|
||||
@@ -95,7 +98,7 @@ export const Toggle = ({
|
||||
<Typography.Text
|
||||
fontSize="xxs"
|
||||
fontWeight={500}
|
||||
className={cn("mr-5", disabled && "opacity-50")}
|
||||
className={cn("mr-3", disabled && "opacity-50")}
|
||||
>
|
||||
{checked ? onText : offText}
|
||||
</Typography.Text>
|
||||
@@ -104,7 +107,7 @@ export const Toggle = ({
|
||||
<Typography.Text
|
||||
fontSize="xxs"
|
||||
fontWeight={500}
|
||||
className={cn(labelClassName, disabled && "opacity-50")}
|
||||
className={cn("ml-2", disabled && "opacity-50", labelClassName)}
|
||||
>
|
||||
{label}
|
||||
</Typography.Text>
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from "@floating-ui/react";
|
||||
import { useRef, useState, type PropsWithChildren } from "react";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import type { BaseProps } from "../../shared/types";
|
||||
|
||||
type ControlledTooltipProps = {
|
||||
open: boolean;
|
||||
@@ -33,10 +34,9 @@ type TooltipTriggerType = "click" | "hover";
|
||||
type BaseTooltipProps = {
|
||||
text: string;
|
||||
withArrow?: boolean;
|
||||
className?: string;
|
||||
placement?: UseFloatingOptions["placement"];
|
||||
trigger?: TooltipTriggerType;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export type TooltipProps = BaseTooltipProps &
|
||||
(ControlledTooltipProps | UncontrolledTooltipProps);
|
||||
@@ -50,6 +50,7 @@ export const Tooltip = ({
|
||||
open,
|
||||
setOpen: setOpenProp,
|
||||
trigger = "hover",
|
||||
testId,
|
||||
}: PropsWithChildren<TooltipProps>) => {
|
||||
const [localOpen, setLocalOpen] = useState(false);
|
||||
const arrowRef = useRef(null);
|
||||
@@ -95,6 +96,7 @@ export const Tooltip = ({
|
||||
ref={refs.setReference}
|
||||
{...getReferenceProps()}
|
||||
className={className}
|
||||
data-testid={testId}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
type FontWeight,
|
||||
} from "./utils";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import type { BaseProps } from "../../shared/types";
|
||||
|
||||
type SupportedReactNodes = "h6" | "h5" | "h4" | "h3" | "h2" | "h1" | "span";
|
||||
|
||||
@@ -13,7 +14,7 @@ export type BaseTypographyProps = React.HTMLAttributes<HTMLElement> & {
|
||||
fontSize?: FontSize;
|
||||
fontWeight?: FontWeight;
|
||||
as: SupportedReactNodes;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const BaseTypography = ({
|
||||
fontSize,
|
||||
@@ -21,6 +22,7 @@ export const BaseTypography = ({
|
||||
className,
|
||||
children,
|
||||
as,
|
||||
testId,
|
||||
...props
|
||||
}: PropsWithChildren<BaseTypographyProps>) => {
|
||||
const Component = as;
|
||||
@@ -28,6 +30,7 @@ export const BaseTypography = ({
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
"tg-family-outfit text-white leading-[100%]",
|
||||
fontSize ? fontSizes[fontSize] : undefined,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"email": "stephan@all-hands.dev"
|
||||
}
|
||||
],
|
||||
"version": "1.0.0-beta.5",
|
||||
"version": "1.0.0-beta.8",
|
||||
"description": "OpenHands UI Components",
|
||||
"keywords": [
|
||||
"openhands",
|
||||
@@ -60,6 +60,7 @@
|
||||
"@storybook/react-vite": "^9.0.12",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"@types/bun": "latest",
|
||||
"@types/deep-equal": "^1.0.4",
|
||||
"@vitejs/plugin-react": "^4.5.2",
|
||||
"@vitest/browser": "^3.2.4",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
@@ -70,19 +71,20 @@
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18.0.0",
|
||||
"react-dom": ">=18.0.0"
|
||||
"react": ">=19.1.0",
|
||||
"react-dom": ">=19.1.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.27.12",
|
||||
"clsx": "^2.1.1",
|
||||
"deep-equal": "^2.2.3",
|
||||
"focus-trap-react": "^11.0.4",
|
||||
"react-bootstrap-icons": "^1.11.6",
|
||||
"react-select": "^5.10.2",
|
||||
"sonner": "^2.0.6",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-scrollbar": "^4.0.2",
|
||||
"tailwindcss": "^4.1.10"
|
||||
"tailwind-scrollbar": "^4.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "storybook dev -p 6006",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import deepEqual from "deep-equal";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
type ArrayActions<T> = {
|
||||
@@ -40,8 +41,8 @@ export function useArray<T>(initialValue: T | T[]): [T[], ArrayActions<T>] {
|
||||
setArray((prev) => {
|
||||
const index = prev.findIndex((item) =>
|
||||
compareBy
|
||||
? isEqual(item[compareBy], value[compareBy])
|
||||
: isEqual(item, value)
|
||||
? deepEqual(item[compareBy], value[compareBy])
|
||||
: deepEqual(item, value)
|
||||
);
|
||||
return index >= 0
|
||||
? [...prev.slice(0, index), ...prev.slice(index + 1)]
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
export type BaseProps = {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
testId?: string;
|
||||
};
|
||||
|
||||
export type HTMLProps<T extends React.ElementType> = Omit<
|
||||
React.ComponentPropsWithoutRef<T>,
|
||||
"children"
|
||||
"children" | "style" | "className"
|
||||
>;
|
||||
|
||||
export type ComponentVariant = "primary" | "secondary" | "tertiary";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { cloneElement, isValidElement, type ReactElement } from "react";
|
||||
import type { ComponentVariant, HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
|
||||
export const cloneIcon = (
|
||||
icon?: ReactElement<HTMLProps<"svg">>,
|
||||
props?: HTMLProps<"svg">
|
||||
icon?: ReactElement<HTMLProps<"svg"> & BaseProps>,
|
||||
props?: HTMLProps<"svg"> & BaseProps
|
||||
) => {
|
||||
if (!icon) {
|
||||
return null;
|
||||
|
||||
@@ -51,6 +51,7 @@ Your primary role is to assist users by executing commands, modifying code, and
|
||||
3. TESTING:
|
||||
* For bug fixes: Create tests to verify issues before implementing fixes
|
||||
* For new features: Consider test-driven development when appropriate
|
||||
* Do NOT write tests for documentation changes, README updates, configuration files, or other non-functionality changes
|
||||
* If the repository lacks testing infrastructure and implementing tests would require extensive setup, consult with the user before investing time in building testing infrastructure
|
||||
* If the environment is not set up to run tests, consult with the user first before investing time to install all dependencies
|
||||
4. IMPLEMENTATION:
|
||||
|
||||
@@ -45,6 +45,7 @@ Your primary role is to assist users by executing commands, modifying code, and
|
||||
3. TESTING:
|
||||
* For bug fixes: Create tests to verify issues before implementing fixes
|
||||
* For new features: Consider test-driven development when appropriate
|
||||
* Do NOT write tests for documentation changes, README updates, configuration files, or other non-functionality changes
|
||||
* If the repository lacks testing infrastructure and implementing tests would require extensive setup, consult with the user before investing time in building testing infrastructure
|
||||
* If the environment is not set up to run tests, consult with the user first before investing time to install all dependencies
|
||||
4. IMPLEMENTATION: Make focused, minimal changes to address the problem
|
||||
|
||||
@@ -44,6 +44,7 @@ Your primary role is to assist users by executing commands, modifying code, and
|
||||
3. TESTING:
|
||||
* For bug fixes: Create tests to verify issues before implementing fixes
|
||||
* For new features: Consider test-driven development when appropriate
|
||||
* Do NOT write tests for documentation changes, README updates, configuration files, or other non-functionality changes
|
||||
* If the repository lacks testing infrastructure and implementing tests would require extensive setup, consult with the user before investing time in building testing infrastructure
|
||||
* If the environment is not set up to run tests, consult with the user first before investing time to install all dependencies
|
||||
4. IMPLEMENTATION: Make focused, minimal changes to address the problem
|
||||
|
||||
+512
-1
@@ -1,9 +1,15 @@
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from prompt_toolkit import print_formatted_text
|
||||
import toml
|
||||
from prompt_toolkit import HTML, print_formatted_text
|
||||
from prompt_toolkit.patch_stdout import patch_stdout
|
||||
from prompt_toolkit.shortcuts import clear, print_container
|
||||
from prompt_toolkit.widgets import Frame, TextArea
|
||||
from pydantic import ValidationError
|
||||
|
||||
from openhands.cli.settings import (
|
||||
display_settings,
|
||||
@@ -14,9 +20,12 @@ from openhands.cli.tui import (
|
||||
COLOR_GREY,
|
||||
UsageMetrics,
|
||||
cli_confirm,
|
||||
create_prompt_session,
|
||||
display_help,
|
||||
display_mcp_errors,
|
||||
display_shutdown_message,
|
||||
display_status,
|
||||
read_prompt_input,
|
||||
)
|
||||
from openhands.cli.utils import (
|
||||
add_local_config_trusted_dir,
|
||||
@@ -27,6 +36,11 @@ from openhands.cli.utils import (
|
||||
from openhands.core.config import (
|
||||
OpenHandsConfig,
|
||||
)
|
||||
from openhands.core.config.mcp_config import (
|
||||
MCPSHTTPServerConfig,
|
||||
MCPSSEServerConfig,
|
||||
MCPStdioServerConfig,
|
||||
)
|
||||
from openhands.core.schema import AgentState
|
||||
from openhands.core.schema.exit_reason import ExitReason
|
||||
from openhands.events import EventSource
|
||||
@@ -38,6 +52,72 @@ from openhands.events.stream import EventStream
|
||||
from openhands.storage.settings.file_settings_store import FileSettingsStore
|
||||
|
||||
|
||||
async def collect_input(config: OpenHandsConfig, prompt_text: str) -> str | None:
|
||||
"""Collect user input with cancellation support.
|
||||
|
||||
Args:
|
||||
config: OpenHands configuration
|
||||
prompt_text: Text to display to user
|
||||
|
||||
Returns:
|
||||
str | None: User input string, or None if user cancelled
|
||||
"""
|
||||
print_formatted_text(prompt_text, end=' ')
|
||||
user_input = await read_prompt_input(config, '', multiline=False)
|
||||
|
||||
# Check for cancellation
|
||||
if user_input.strip().lower() in ['/exit', '/cancel', 'cancel']:
|
||||
return None
|
||||
|
||||
return user_input.strip()
|
||||
|
||||
|
||||
def restart_cli() -> None:
|
||||
"""Restart the CLI by replacing the current process."""
|
||||
print_formatted_text('🔄 Restarting OpenHands CLI...')
|
||||
|
||||
# Get the current Python executable and script arguments
|
||||
python_executable = sys.executable
|
||||
script_args = sys.argv
|
||||
|
||||
# Use os.execv to replace the current process
|
||||
# This preserves the original command line arguments
|
||||
try:
|
||||
os.execv(python_executable, [python_executable] + script_args)
|
||||
except Exception as e:
|
||||
print_formatted_text(f'❌ Failed to restart CLI: {e}')
|
||||
print_formatted_text(
|
||||
'Please restart OpenHands manually for changes to take effect.'
|
||||
)
|
||||
|
||||
|
||||
async def prompt_for_restart(config: OpenHandsConfig) -> bool:
|
||||
"""Prompt user if they want to restart the CLI and return their choice."""
|
||||
print_formatted_text('📝 MCP server configuration updated successfully!')
|
||||
print_formatted_text('The changes will take effect after restarting OpenHands.')
|
||||
|
||||
prompt_session = create_prompt_session(config)
|
||||
|
||||
while True:
|
||||
try:
|
||||
with patch_stdout():
|
||||
response = await prompt_session.prompt_async(
|
||||
HTML(
|
||||
'<gold>Would you like to restart OpenHands now? (y/n): </gold>'
|
||||
)
|
||||
)
|
||||
response = response.strip().lower() if response else ''
|
||||
|
||||
if response in ['y', 'yes']:
|
||||
return True
|
||||
elif response in ['n', 'no']:
|
||||
return False
|
||||
else:
|
||||
print_formatted_text('Please enter "y" for yes or "n" for no.')
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
return False
|
||||
|
||||
|
||||
async def handle_commands(
|
||||
command: str,
|
||||
event_stream: EventStream,
|
||||
@@ -79,6 +159,8 @@ async def handle_commands(
|
||||
await handle_settings_command(config, settings_store)
|
||||
elif command == '/resume':
|
||||
close_repl, new_session_requested = await handle_resume_command(event_stream)
|
||||
elif command == '/mcp':
|
||||
await handle_mcp_command(config)
|
||||
else:
|
||||
close_repl = True
|
||||
action = MessageAction(content=command)
|
||||
@@ -327,3 +409,432 @@ def check_folder_security_agreement(config: OpenHandsConfig, current_dir: str) -
|
||||
return confirm
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def handle_mcp_command(config: OpenHandsConfig) -> None:
|
||||
"""Handle MCP command with interactive menu."""
|
||||
action = cli_confirm(
|
||||
config,
|
||||
'MCP Server Configuration',
|
||||
[
|
||||
'List configured servers',
|
||||
'Add new server',
|
||||
'Remove server',
|
||||
'View errors',
|
||||
'Go back',
|
||||
],
|
||||
)
|
||||
|
||||
if action == 0: # List
|
||||
display_mcp_servers(config)
|
||||
elif action == 1: # Add
|
||||
await add_mcp_server(config)
|
||||
elif action == 2: # Remove
|
||||
await remove_mcp_server(config)
|
||||
elif action == 3: # View errors
|
||||
handle_mcp_errors_command()
|
||||
# action == 4 is "Go back", do nothing
|
||||
|
||||
|
||||
def display_mcp_servers(config: OpenHandsConfig) -> None:
|
||||
"""Display MCP server configuration information."""
|
||||
mcp_config = config.mcp
|
||||
|
||||
# Count the different types of servers
|
||||
sse_count = len(mcp_config.sse_servers)
|
||||
stdio_count = len(mcp_config.stdio_servers)
|
||||
shttp_count = len(mcp_config.shttp_servers)
|
||||
total_count = sse_count + stdio_count + shttp_count
|
||||
|
||||
if total_count == 0:
|
||||
print_formatted_text(
|
||||
'No custom MCP servers configured. See the documentation to learn more:\n'
|
||||
' https://docs.all-hands.dev/usage/how-to/cli-mode#using-mcp-servers'
|
||||
)
|
||||
else:
|
||||
print_formatted_text(
|
||||
f'Configured MCP servers:\n'
|
||||
f' • SSE servers: {sse_count}\n'
|
||||
f' • Stdio servers: {stdio_count}\n'
|
||||
f' • SHTTP servers: {shttp_count}\n'
|
||||
f' • Total: {total_count}'
|
||||
)
|
||||
|
||||
# Show details for each type if they exist
|
||||
if sse_count > 0:
|
||||
print_formatted_text('SSE Servers:')
|
||||
for idx, sse_server in enumerate(mcp_config.sse_servers, 1):
|
||||
print_formatted_text(f' {idx}. {sse_server.url}')
|
||||
print_formatted_text('')
|
||||
|
||||
if stdio_count > 0:
|
||||
print_formatted_text('Stdio Servers:')
|
||||
for idx, stdio_server in enumerate(mcp_config.stdio_servers, 1):
|
||||
print_formatted_text(
|
||||
f' {idx}. {stdio_server.name} ({stdio_server.command})'
|
||||
)
|
||||
print_formatted_text('')
|
||||
|
||||
if shttp_count > 0:
|
||||
print_formatted_text('SHTTP Servers:')
|
||||
for idx, shttp_server in enumerate(mcp_config.shttp_servers, 1):
|
||||
print_formatted_text(f' {idx}. {shttp_server.url}')
|
||||
print_formatted_text('')
|
||||
|
||||
|
||||
def handle_mcp_errors_command() -> None:
|
||||
"""Display MCP connection errors."""
|
||||
display_mcp_errors()
|
||||
|
||||
|
||||
def get_config_file_path() -> Path:
|
||||
"""Get the path to the config file. By default, we use config.toml in the current working directory. If not found, we use ~/.openhands/config.toml."""
|
||||
# Check if config.toml exists in the current directory
|
||||
current_dir = Path.cwd() / 'config.toml'
|
||||
if current_dir.exists():
|
||||
return current_dir
|
||||
|
||||
# Fallback to the user's home directory
|
||||
return Path.home() / '.openhands' / 'config.toml'
|
||||
|
||||
|
||||
def load_config_file(file_path: Path) -> dict:
|
||||
"""Load the config file, creating it if it doesn't exist."""
|
||||
if file_path.exists():
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
return toml.load(f)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Create directory if it doesn't exist
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
return {}
|
||||
|
||||
|
||||
def save_config_file(config_data: dict, file_path: Path) -> None:
|
||||
"""Save the config file."""
|
||||
with open(file_path, 'w') as f:
|
||||
toml.dump(config_data, f)
|
||||
|
||||
|
||||
def _ensure_mcp_config_structure(config_data: dict) -> None:
|
||||
"""Ensure MCP configuration structure exists in config data."""
|
||||
if 'mcp' not in config_data:
|
||||
config_data['mcp'] = {}
|
||||
|
||||
|
||||
def _add_server_to_config(server_type: str, server_config: dict) -> Path:
|
||||
"""Add a server configuration to the config file."""
|
||||
config_file_path = get_config_file_path()
|
||||
config_data = load_config_file(config_file_path)
|
||||
_ensure_mcp_config_structure(config_data)
|
||||
|
||||
if server_type not in config_data['mcp']:
|
||||
config_data['mcp'][server_type] = []
|
||||
|
||||
config_data['mcp'][server_type].append(server_config)
|
||||
save_config_file(config_data, config_file_path)
|
||||
|
||||
return config_file_path
|
||||
|
||||
|
||||
async def add_mcp_server(config: OpenHandsConfig) -> None:
|
||||
"""Add a new MCP server configuration."""
|
||||
# Choose transport type
|
||||
transport_type = cli_confirm(
|
||||
config,
|
||||
'Select MCP server transport type:',
|
||||
[
|
||||
'SSE (Server-Sent Events)',
|
||||
'Stdio (Standard Input/Output)',
|
||||
'SHTTP (Streamable HTTP)',
|
||||
'Cancel',
|
||||
],
|
||||
)
|
||||
|
||||
if transport_type == 3: # Cancel
|
||||
return
|
||||
|
||||
try:
|
||||
if transport_type == 0: # SSE
|
||||
await add_sse_server(config)
|
||||
elif transport_type == 1: # Stdio
|
||||
await add_stdio_server(config)
|
||||
elif transport_type == 2: # SHTTP
|
||||
await add_shttp_server(config)
|
||||
except Exception as e:
|
||||
print_formatted_text(f'Error adding MCP server: {e}')
|
||||
|
||||
|
||||
async def add_sse_server(config: OpenHandsConfig) -> None:
|
||||
"""Add an SSE MCP server."""
|
||||
print_formatted_text('Adding SSE MCP Server')
|
||||
|
||||
while True: # Retry loop for the entire form
|
||||
# Collect all inputs
|
||||
url = await collect_input(config, '\nEnter server URL:')
|
||||
if url is None:
|
||||
print_formatted_text('Operation cancelled.')
|
||||
return
|
||||
|
||||
api_key = await collect_input(
|
||||
config, '\nEnter API key (optional, press Enter to skip):'
|
||||
)
|
||||
if api_key is None:
|
||||
print_formatted_text('Operation cancelled.')
|
||||
return
|
||||
|
||||
# Convert empty string to None for optional field
|
||||
api_key = api_key if api_key else None
|
||||
|
||||
# Validate all inputs at once
|
||||
try:
|
||||
server = MCPSSEServerConfig(url=url, api_key=api_key)
|
||||
break # Success - exit retry loop
|
||||
|
||||
except ValidationError as e:
|
||||
# Show all errors at once
|
||||
print_formatted_text('❌ Please fix the following errors:')
|
||||
for error in e.errors():
|
||||
field = error['loc'][0] if error['loc'] else 'unknown'
|
||||
print_formatted_text(f' • {field}: {error["msg"]}')
|
||||
|
||||
if cli_confirm(config, '\nTry again?') != 0:
|
||||
print_formatted_text('Operation cancelled.')
|
||||
return
|
||||
|
||||
# Save to config file
|
||||
server_config = {'url': server.url}
|
||||
if server.api_key:
|
||||
server_config['api_key'] = server.api_key
|
||||
|
||||
config_file_path = _add_server_to_config('sse_servers', server_config)
|
||||
print_formatted_text(f'✓ SSE MCP server added to {config_file_path}: {server.url}')
|
||||
|
||||
# Prompt for restart
|
||||
if await prompt_for_restart(config):
|
||||
restart_cli()
|
||||
|
||||
|
||||
async def add_stdio_server(config: OpenHandsConfig) -> None:
|
||||
"""Add a Stdio MCP server."""
|
||||
print_formatted_text('Adding Stdio MCP Server')
|
||||
|
||||
# Get existing server names to check for duplicates
|
||||
existing_names = [server.name for server in config.mcp.stdio_servers]
|
||||
|
||||
while True: # Retry loop for the entire form
|
||||
# Collect all inputs
|
||||
name = await collect_input(config, '\nEnter server name:')
|
||||
if name is None:
|
||||
print_formatted_text('Operation cancelled.')
|
||||
return
|
||||
|
||||
command = await collect_input(config, "\nEnter command (e.g., 'uvx', 'npx'):")
|
||||
if command is None:
|
||||
print_formatted_text('Operation cancelled.')
|
||||
return
|
||||
|
||||
args_input = await collect_input(
|
||||
config,
|
||||
'\nEnter arguments (optional, e.g., "-y server-package arg1"):',
|
||||
)
|
||||
if args_input is None:
|
||||
print_formatted_text('Operation cancelled.')
|
||||
return
|
||||
|
||||
env_input = await collect_input(
|
||||
config,
|
||||
'\nEnter environment variables (KEY=VALUE format, comma-separated, optional):',
|
||||
)
|
||||
if env_input is None:
|
||||
print_formatted_text('Operation cancelled.')
|
||||
return
|
||||
|
||||
# Check for duplicate server names
|
||||
if name in existing_names:
|
||||
print_formatted_text(f"❌ Server name '{name}' already exists.")
|
||||
if cli_confirm(config, '\nTry again?') != 0:
|
||||
print_formatted_text('Operation cancelled.')
|
||||
return
|
||||
continue
|
||||
|
||||
# Validate all inputs at once
|
||||
try:
|
||||
server = MCPStdioServerConfig(
|
||||
name=name,
|
||||
command=command,
|
||||
args=args_input, # type: ignore # Will be parsed by Pydantic validator
|
||||
env=env_input, # type: ignore # Will be parsed by Pydantic validator
|
||||
)
|
||||
break # Success - exit retry loop
|
||||
|
||||
except ValidationError as e:
|
||||
# Show all errors at once
|
||||
print_formatted_text('❌ Please fix the following errors:')
|
||||
for error in e.errors():
|
||||
field = error['loc'][0] if error['loc'] else 'unknown'
|
||||
print_formatted_text(f' • {field}: {error["msg"]}')
|
||||
|
||||
if cli_confirm(config, '\nTry again?') != 0:
|
||||
print_formatted_text('Operation cancelled.')
|
||||
return
|
||||
|
||||
# Save to config file
|
||||
server_config: dict[str, Any] = {
|
||||
'name': server.name,
|
||||
'command': server.command,
|
||||
}
|
||||
if server.args:
|
||||
server_config['args'] = server.args
|
||||
if server.env:
|
||||
server_config['env'] = server.env
|
||||
|
||||
config_file_path = _add_server_to_config('stdio_servers', server_config)
|
||||
print_formatted_text(
|
||||
f'✓ Stdio MCP server added to {config_file_path}: {server.name}'
|
||||
)
|
||||
|
||||
# Prompt for restart
|
||||
if await prompt_for_restart(config):
|
||||
restart_cli()
|
||||
|
||||
|
||||
async def add_shttp_server(config: OpenHandsConfig) -> None:
|
||||
"""Add an SHTTP MCP server."""
|
||||
print_formatted_text('Adding SHTTP MCP Server')
|
||||
|
||||
while True: # Retry loop for the entire form
|
||||
# Collect all inputs
|
||||
url = await collect_input(config, '\nEnter server URL:')
|
||||
if url is None:
|
||||
print_formatted_text('Operation cancelled.')
|
||||
return
|
||||
|
||||
api_key = await collect_input(
|
||||
config, '\nEnter API key (optional, press Enter to skip):'
|
||||
)
|
||||
if api_key is None:
|
||||
print_formatted_text('Operation cancelled.')
|
||||
return
|
||||
|
||||
# Convert empty string to None for optional field
|
||||
api_key = api_key if api_key else None
|
||||
|
||||
# Validate all inputs at once
|
||||
try:
|
||||
server = MCPSHTTPServerConfig(url=url, api_key=api_key)
|
||||
break # Success - exit retry loop
|
||||
|
||||
except ValidationError as e:
|
||||
# Show all errors at once
|
||||
print_formatted_text('❌ Please fix the following errors:')
|
||||
for error in e.errors():
|
||||
field = error['loc'][0] if error['loc'] else 'unknown'
|
||||
print_formatted_text(f' • {field}: {error["msg"]}')
|
||||
|
||||
if cli_confirm(config, '\nTry again?') != 0:
|
||||
print_formatted_text('Operation cancelled.')
|
||||
return
|
||||
|
||||
# Save to config file
|
||||
server_config = {'url': server.url}
|
||||
if server.api_key:
|
||||
server_config['api_key'] = server.api_key
|
||||
|
||||
config_file_path = _add_server_to_config('shttp_servers', server_config)
|
||||
print_formatted_text(
|
||||
f'✓ SHTTP MCP server added to {config_file_path}: {server.url}'
|
||||
)
|
||||
|
||||
# Prompt for restart
|
||||
if await prompt_for_restart(config):
|
||||
restart_cli()
|
||||
|
||||
|
||||
async def remove_mcp_server(config: OpenHandsConfig) -> None:
|
||||
"""Remove an MCP server configuration."""
|
||||
mcp_config = config.mcp
|
||||
|
||||
# Collect all servers with their types
|
||||
servers: list[tuple[str, str, object]] = []
|
||||
|
||||
# Add SSE servers
|
||||
for sse_server in mcp_config.sse_servers:
|
||||
servers.append(('SSE', sse_server.url, sse_server))
|
||||
|
||||
# Add Stdio servers
|
||||
for stdio_server in mcp_config.stdio_servers:
|
||||
servers.append(('Stdio', stdio_server.name, stdio_server))
|
||||
|
||||
# Add SHTTP servers
|
||||
for shttp_server in mcp_config.shttp_servers:
|
||||
servers.append(('SHTTP', shttp_server.url, shttp_server))
|
||||
|
||||
if not servers:
|
||||
print_formatted_text('No MCP servers configured to remove.')
|
||||
return
|
||||
|
||||
# Create choices for the user
|
||||
choices = []
|
||||
for server_type, identifier, _ in servers:
|
||||
choices.append(f'{server_type}: {identifier}')
|
||||
choices.append('Cancel')
|
||||
|
||||
# Let user choose which server to remove
|
||||
choice = cli_confirm(config, 'Select MCP server to remove:', choices)
|
||||
|
||||
if choice == len(choices) - 1: # Cancel
|
||||
return
|
||||
|
||||
# Remove the selected server
|
||||
server_type, identifier, _ = servers[choice]
|
||||
|
||||
# Confirm removal
|
||||
confirm = cli_confirm(
|
||||
config,
|
||||
f'Are you sure you want to remove {server_type} server "{identifier}"?',
|
||||
['Yes, remove', 'Cancel'],
|
||||
)
|
||||
|
||||
if confirm == 1: # Cancel
|
||||
return
|
||||
|
||||
# Load config file and remove the server
|
||||
config_file_path = get_config_file_path()
|
||||
config_data = load_config_file(config_file_path)
|
||||
|
||||
_ensure_mcp_config_structure(config_data)
|
||||
|
||||
removed = False
|
||||
|
||||
if server_type == 'SSE' and 'sse_servers' in config_data['mcp']:
|
||||
config_data['mcp']['sse_servers'] = [
|
||||
s for s in config_data['mcp']['sse_servers'] if s.get('url') != identifier
|
||||
]
|
||||
removed = True
|
||||
elif server_type == 'Stdio' and 'stdio_servers' in config_data['mcp']:
|
||||
config_data['mcp']['stdio_servers'] = [
|
||||
s
|
||||
for s in config_data['mcp']['stdio_servers']
|
||||
if s.get('name') != identifier
|
||||
]
|
||||
removed = True
|
||||
elif server_type == 'SHTTP' and 'shttp_servers' in config_data['mcp']:
|
||||
config_data['mcp']['shttp_servers'] = [
|
||||
s for s in config_data['mcp']['shttp_servers'] if s.get('url') != identifier
|
||||
]
|
||||
removed = True
|
||||
|
||||
if removed:
|
||||
save_config_file(config_data, config_file_path)
|
||||
print_formatted_text(
|
||||
f'✓ {server_type} MCP server "{identifier}" removed from {config_file_path}.'
|
||||
)
|
||||
|
||||
# Prompt for restart
|
||||
if await prompt_for_restart(config):
|
||||
restart_cli()
|
||||
else:
|
||||
print_formatted_text(f'Failed to remove {server_type} server "{identifier}".')
|
||||
|
||||
+41
-10
@@ -74,6 +74,7 @@ from openhands.events.observation import (
|
||||
)
|
||||
from openhands.io import read_task
|
||||
from openhands.mcp import add_mcp_tools_to_agent
|
||||
from openhands.mcp.error_collector import mcp_error_collector
|
||||
from openhands.memory.condenser.impl.llm_summarizing_condenser import (
|
||||
LLMSummarizingCondenserConfig,
|
||||
)
|
||||
@@ -238,7 +239,7 @@ async def run_session(
|
||||
ChangeAgentStateAction(AgentState.USER_CONFIRMED),
|
||||
EventSource.USER,
|
||||
)
|
||||
elif confirmation_status == 'edit':
|
||||
else: # 'no' or alternative instructions
|
||||
# Tell the agent the proposed action was rejected
|
||||
event_stream.add_event(
|
||||
ChangeAgentStateAction(AgentState.USER_REJECTED),
|
||||
@@ -247,16 +248,9 @@ async def run_session(
|
||||
# Notify the user
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<skyblue>Okay, please tell me what I should do instead.</skyblue>'
|
||||
'<skyblue>Okay, please tell me what I should do next/instead.</skyblue>'
|
||||
)
|
||||
)
|
||||
# Solicit replacement isntructions
|
||||
await prompt_for_next_task(AgentState.AWAITING_USER_INPUT)
|
||||
else: # 'no' or fallback
|
||||
event_stream.add_event(
|
||||
ChangeAgentStateAction(AgentState.USER_REJECTED),
|
||||
EventSource.USER,
|
||||
)
|
||||
|
||||
# Set the always_confirm_mode flag if the user wants to always confirm
|
||||
if confirmation_status == 'always':
|
||||
@@ -298,6 +292,10 @@ async def run_session(
|
||||
|
||||
# Add MCP tools to the agent
|
||||
if agent.config.enable_mcp:
|
||||
# Clear any previous errors and enable collection
|
||||
mcp_error_collector.clear_errors()
|
||||
mcp_error_collector.enable_collection()
|
||||
|
||||
# Add OpenHands' MCP server by default
|
||||
_, openhands_mcp_stdio_servers = (
|
||||
OpenHandsMCPConfigImpl.create_default_mcp_server_config(
|
||||
@@ -309,6 +307,9 @@ async def run_session(
|
||||
|
||||
await add_mcp_tools_to_agent(agent, runtime, memory)
|
||||
|
||||
# Disable collection after startup
|
||||
mcp_error_collector.disable_collection()
|
||||
|
||||
# Clear loading animation
|
||||
is_loaded.set()
|
||||
|
||||
@@ -319,7 +320,27 @@ async def run_session(
|
||||
if not skip_banner:
|
||||
display_banner(session_id=sid)
|
||||
|
||||
welcome_message = 'What do you want to build?' # from the application
|
||||
welcome_message = ''
|
||||
|
||||
# Display number of MCP servers configured
|
||||
if agent.config.enable_mcp:
|
||||
total_mcp_servers = (
|
||||
len(runtime.config.mcp.stdio_servers)
|
||||
+ len(runtime.config.mcp.sse_servers)
|
||||
+ len(runtime.config.mcp.shttp_servers)
|
||||
)
|
||||
if total_mcp_servers > 0:
|
||||
mcp_line = f'Using {len(runtime.config.mcp.stdio_servers)} stdio MCP servers, {len(runtime.config.mcp.sse_servers)} SSE MCP servers and {len(runtime.config.mcp.shttp_servers)} SHTTP MCP servers.'
|
||||
|
||||
# Check for MCP errors and add indicator to the same line
|
||||
if agent.config.enable_mcp and mcp_error_collector.has_errors():
|
||||
mcp_line += (
|
||||
' ✗ MCP errors detected (type /mcp → select View errors to view)'
|
||||
)
|
||||
|
||||
welcome_message += mcp_line + '\n\n'
|
||||
|
||||
welcome_message += 'What do you want to build?' # from the application
|
||||
initial_message = '' # from the user
|
||||
|
||||
if task_content:
|
||||
@@ -488,6 +509,16 @@ async def main_with_loop(loop: asyncio.AbstractEventLoop) -> None:
|
||||
if not env_log_level:
|
||||
logger.setLevel(logging.WARNING)
|
||||
|
||||
# If `config.toml` does not exist in current directory, use the file under home directory
|
||||
if not os.path.exists(args.config_file):
|
||||
home_config_file = os.path.join(
|
||||
os.path.expanduser('~'), '.openhands', 'config.toml'
|
||||
)
|
||||
logger.info(
|
||||
f'Config file {args.config_file} does not exist, using default config file in home directory: {home_config_file}.'
|
||||
)
|
||||
args.config_file = home_config_file
|
||||
|
||||
# Load config from toml and override with command line arguments
|
||||
config: OpenHandsConfig = setup_config_from_args(args)
|
||||
|
||||
|
||||
+115
-3
@@ -4,6 +4,8 @@
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import datetime
|
||||
import json
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
@@ -36,6 +38,7 @@ from openhands.events.action import (
|
||||
ActionConfirmationStatus,
|
||||
ChangeAgentStateAction,
|
||||
CmdRunAction,
|
||||
MCPAction,
|
||||
MessageAction,
|
||||
)
|
||||
from openhands.events.event import Event
|
||||
@@ -45,8 +48,10 @@ from openhands.events.observation import (
|
||||
ErrorObservation,
|
||||
FileEditObservation,
|
||||
FileReadObservation,
|
||||
MCPObservation,
|
||||
)
|
||||
from openhands.llm.metrics import Metrics
|
||||
from openhands.mcp.error_collector import mcp_error_collector
|
||||
|
||||
ENABLE_STREAMING = False # FIXME: this doesn't work
|
||||
|
||||
@@ -76,6 +81,7 @@ COMMANDS = {
|
||||
'/new': 'Create a new conversation',
|
||||
'/settings': 'Display and modify current settings',
|
||||
'/resume': 'Resume the agent when paused',
|
||||
'/mcp': 'Manage MCP server configuration and view errors',
|
||||
}
|
||||
|
||||
print_lock = threading.Lock()
|
||||
@@ -162,6 +168,7 @@ def display_welcome_message(message: str = '') -> None:
|
||||
print_formatted_text(
|
||||
HTML("<gold>Let's start building!</gold>\n"), style=DEFAULT_STYLE
|
||||
)
|
||||
|
||||
if message:
|
||||
print_formatted_text(
|
||||
HTML(f'{message} <grey>Type /help for help</grey>'),
|
||||
@@ -186,6 +193,48 @@ def display_initial_user_prompt(prompt: str) -> None:
|
||||
)
|
||||
|
||||
|
||||
def display_mcp_errors() -> None:
|
||||
"""Display collected MCP errors."""
|
||||
errors = mcp_error_collector.get_errors()
|
||||
|
||||
if not errors:
|
||||
print_formatted_text(HTML('<ansigreen>✓ No MCP errors detected</ansigreen>\n'))
|
||||
return
|
||||
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
f'<ansired>✗ {len(errors)} MCP error(s) detected during startup:</ansired>\n'
|
||||
)
|
||||
)
|
||||
|
||||
for i, error in enumerate(errors, 1):
|
||||
# Format timestamp
|
||||
timestamp = datetime.datetime.fromtimestamp(error.timestamp).strftime(
|
||||
'%H:%M:%S'
|
||||
)
|
||||
|
||||
# Create error display text
|
||||
error_text = (
|
||||
f'[{timestamp}] {error.server_type.upper()} Server: {error.server_name}\n'
|
||||
)
|
||||
error_text += f'Error: {error.error_message}\n'
|
||||
if error.exception_details:
|
||||
error_text += f'Details: {error.exception_details}'
|
||||
|
||||
container = Frame(
|
||||
TextArea(
|
||||
text=error_text,
|
||||
read_only=True,
|
||||
style='ansired',
|
||||
wrap_lines=True,
|
||||
),
|
||||
title=f'MCP Error #{i}',
|
||||
style='ansired',
|
||||
)
|
||||
print_container(container)
|
||||
print_formatted_text('') # Add spacing between errors
|
||||
|
||||
|
||||
# Prompt output display functions
|
||||
def display_thought_if_new(thought: str) -> None:
|
||||
"""Display a thought only if it hasn't been displayed recently."""
|
||||
@@ -215,6 +264,8 @@ def display_event(event: Event, config: OpenHandsConfig) -> None:
|
||||
|
||||
if event.confirmation_state == ActionConfirmationStatus.CONFIRMED:
|
||||
initialize_streaming_output()
|
||||
elif isinstance(event, MCPAction):
|
||||
display_mcp_action(event)
|
||||
elif isinstance(event, Action):
|
||||
# For other actions, display thoughts normally
|
||||
if hasattr(event, 'thought') and event.thought:
|
||||
@@ -232,6 +283,8 @@ def display_event(event: Event, config: OpenHandsConfig) -> None:
|
||||
display_file_edit(event)
|
||||
elif isinstance(event, FileReadObservation):
|
||||
display_file_read(event)
|
||||
elif isinstance(event, MCPObservation):
|
||||
display_mcp_observation(event)
|
||||
elif isinstance(event, AgentStateChangedObservation):
|
||||
display_agent_state_change_message(event.agent_state)
|
||||
elif isinstance(event, ErrorObservation):
|
||||
@@ -337,6 +390,66 @@ def display_file_read(event: FileReadObservation) -> None:
|
||||
print_container(container)
|
||||
|
||||
|
||||
def display_mcp_action(event: MCPAction) -> None:
|
||||
"""Display an MCP action in the CLI."""
|
||||
# Format the arguments for display
|
||||
args_text = ''
|
||||
if event.arguments:
|
||||
try:
|
||||
args_text = json.dumps(event.arguments, indent=2)
|
||||
except (TypeError, ValueError):
|
||||
args_text = str(event.arguments)
|
||||
|
||||
# Create the display text
|
||||
display_text = f'Tool: {event.name}'
|
||||
if args_text:
|
||||
display_text += f'\n\nArguments:\n{args_text}'
|
||||
|
||||
container = Frame(
|
||||
TextArea(
|
||||
text=display_text,
|
||||
read_only=True,
|
||||
style='ansiblue',
|
||||
wrap_lines=True,
|
||||
),
|
||||
title='MCP Tool Call',
|
||||
style='ansiblue',
|
||||
)
|
||||
print_formatted_text('')
|
||||
print_container(container)
|
||||
|
||||
|
||||
def display_mcp_observation(event: MCPObservation) -> None:
|
||||
"""Display an MCP observation in the CLI."""
|
||||
# Format the content for display
|
||||
content = event.content.strip() if event.content else 'No output'
|
||||
|
||||
# Add tool name and arguments info if available
|
||||
display_text = content
|
||||
if event.name:
|
||||
header = f'Tool: {event.name}'
|
||||
if event.arguments:
|
||||
try:
|
||||
args_text = json.dumps(event.arguments, indent=2)
|
||||
header += f'\nArguments: {args_text}'
|
||||
except (TypeError, ValueError):
|
||||
header += f'\nArguments: {event.arguments}'
|
||||
display_text = f'{header}\n\nResult:\n{content}'
|
||||
|
||||
container = Frame(
|
||||
TextArea(
|
||||
text=display_text,
|
||||
read_only=True,
|
||||
style=COLOR_GREY,
|
||||
wrap_lines=True,
|
||||
),
|
||||
title='MCP Tool Result',
|
||||
style=f'fg:{COLOR_GREY}',
|
||||
)
|
||||
print_formatted_text('')
|
||||
print_container(container)
|
||||
|
||||
|
||||
def initialize_streaming_output():
|
||||
"""Initialize the streaming output TextArea."""
|
||||
if not ENABLE_STREAMING:
|
||||
@@ -591,9 +704,8 @@ async def read_confirmation_input(config: OpenHandsConfig) -> str:
|
||||
try:
|
||||
choices = [
|
||||
'Yes, proceed',
|
||||
'No, skip this action',
|
||||
'No (and allow to enter instructions)',
|
||||
"Always proceed (don't ask again)",
|
||||
'Let me provide different instructions',
|
||||
]
|
||||
|
||||
# keep the outer coroutine responsive by using asyncio.to_thread which puts the blocking call app.run() of cli_confirm() in a separate thread
|
||||
@@ -601,7 +713,7 @@ async def read_confirmation_input(config: OpenHandsConfig) -> str:
|
||||
cli_confirm, config, 'Choose an option:', choices
|
||||
)
|
||||
|
||||
return {0: 'yes', 1: 'no', 2: 'always', 3: 'edit'}.get(index, 'no')
|
||||
return {0: 'yes', 1: 'no', 2: 'always'}.get(index, 'no')
|
||||
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
return 'no'
|
||||
|
||||
@@ -57,11 +57,11 @@ class LLMConfig(BaseModel):
|
||||
aws_region_name: str | None = Field(default=None)
|
||||
openrouter_site_url: str = Field(default='https://docs.all-hands.dev/')
|
||||
openrouter_app_name: str = Field(default='OpenHands')
|
||||
# total wait time: 5 + 10 + 20 + 30 = 65 seconds
|
||||
num_retries: int = Field(default=4)
|
||||
retry_multiplier: float = Field(default=2)
|
||||
retry_min_wait: int = Field(default=5)
|
||||
retry_max_wait: int = Field(default=30)
|
||||
# total wait time: 8 + 16 + 32 + 64 = 120 seconds
|
||||
num_retries: int = Field(default=5)
|
||||
retry_multiplier: float = Field(default=8)
|
||||
retry_min_wait: int = Field(default=8)
|
||||
retry_max_wait: int = Field(default=64)
|
||||
timeout: int | None = Field(default=None)
|
||||
max_message_chars: int = Field(
|
||||
default=30_000
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
from typing import TYPE_CHECKING
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, ValidationError, model_validator
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
ConfigDict,
|
||||
Field,
|
||||
ValidationError,
|
||||
field_validator,
|
||||
model_validator,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
@@ -11,6 +20,27 @@ from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.utils.import_utils import get_impl
|
||||
|
||||
|
||||
def _validate_mcp_url(url: str) -> str:
|
||||
"""Shared URL validation logic for MCP servers."""
|
||||
if not url.strip():
|
||||
raise ValueError('URL cannot be empty')
|
||||
|
||||
url = url.strip()
|
||||
try:
|
||||
parsed = urlparse(url)
|
||||
if not parsed.scheme:
|
||||
raise ValueError('URL must include a scheme (http:// or https://)')
|
||||
if not parsed.netloc:
|
||||
raise ValueError('URL must include a valid domain/host')
|
||||
if parsed.scheme not in ['http', 'https', 'ws', 'wss']:
|
||||
raise ValueError('URL scheme must be http, https, ws, or wss')
|
||||
return url
|
||||
except Exception as e:
|
||||
if isinstance(e, ValueError):
|
||||
raise
|
||||
raise ValueError(f'Invalid URL format: {str(e)}')
|
||||
|
||||
|
||||
class MCPSSEServerConfig(BaseModel):
|
||||
"""Configuration for a single MCP server.
|
||||
|
||||
@@ -22,6 +52,12 @@ class MCPSSEServerConfig(BaseModel):
|
||||
url: str
|
||||
api_key: str | None = None
|
||||
|
||||
@field_validator('url')
|
||||
@classmethod
|
||||
def validate_url(cls, v: str) -> str:
|
||||
"""Validate URL format for MCP servers."""
|
||||
return _validate_mcp_url(v)
|
||||
|
||||
|
||||
class MCPStdioServerConfig(BaseModel):
|
||||
"""Configuration for a MCP server that uses stdio.
|
||||
@@ -38,6 +74,100 @@ class MCPStdioServerConfig(BaseModel):
|
||||
args: list[str] = Field(default_factory=list)
|
||||
env: dict[str, str] = Field(default_factory=dict)
|
||||
|
||||
@field_validator('name')
|
||||
@classmethod
|
||||
def validate_server_name(cls, v: str) -> str:
|
||||
"""Validate server name for stdio MCP servers."""
|
||||
if not v.strip():
|
||||
raise ValueError('Server name cannot be empty')
|
||||
|
||||
v = v.strip()
|
||||
|
||||
# Check for valid characters (alphanumeric, hyphens, underscores)
|
||||
if not re.match(r'^[a-zA-Z0-9_-]+$', v):
|
||||
raise ValueError(
|
||||
'Server name can only contain letters, numbers, hyphens, and underscores'
|
||||
)
|
||||
|
||||
return v
|
||||
|
||||
@field_validator('command')
|
||||
@classmethod
|
||||
def validate_command(cls, v: str) -> str:
|
||||
"""Validate command for stdio MCP servers."""
|
||||
if not v.strip():
|
||||
raise ValueError('Command cannot be empty')
|
||||
|
||||
v = v.strip()
|
||||
|
||||
# Check that command doesn't contain spaces (should be a single executable)
|
||||
if ' ' in v:
|
||||
raise ValueError(
|
||||
'Command should be a single executable without spaces (use arguments field for parameters)'
|
||||
)
|
||||
|
||||
return v
|
||||
|
||||
@field_validator('args', mode='before')
|
||||
@classmethod
|
||||
def parse_args(cls, v) -> list[str]:
|
||||
"""Parse arguments from string or return list as-is.
|
||||
|
||||
Supports shell-like argument parsing using shlex.split().
|
||||
Examples:
|
||||
- "-y mcp-remote https://example.com"
|
||||
- '--config "path with spaces" --debug'
|
||||
- "arg1 arg2 arg3"
|
||||
"""
|
||||
if isinstance(v, str):
|
||||
if not v.strip():
|
||||
return []
|
||||
|
||||
v = v.strip()
|
||||
|
||||
# Use shell-like parsing for natural argument handling
|
||||
try:
|
||||
return shlex.split(v)
|
||||
except ValueError as e:
|
||||
# If shlex parsing fails (e.g., unmatched quotes), provide clear error
|
||||
raise ValueError(
|
||||
f'Invalid argument format: {str(e)}. Use shell-like format, e.g., "arg1 arg2" or \'--config "value with spaces"\''
|
||||
)
|
||||
|
||||
return v or []
|
||||
|
||||
@field_validator('env', mode='before')
|
||||
@classmethod
|
||||
def parse_env(cls, v) -> dict[str, str]:
|
||||
"""Parse environment variables from string or return dict as-is."""
|
||||
if isinstance(v, str):
|
||||
if not v.strip():
|
||||
return {}
|
||||
|
||||
env = {}
|
||||
for pair in v.split(','):
|
||||
pair = pair.strip()
|
||||
if not pair:
|
||||
continue
|
||||
|
||||
if '=' not in pair:
|
||||
raise ValueError(
|
||||
f"Environment variable '{pair}' must be in KEY=VALUE format"
|
||||
)
|
||||
|
||||
key, value = pair.split('=', 1)
|
||||
key = key.strip()
|
||||
if not key:
|
||||
raise ValueError('Environment variable key cannot be empty')
|
||||
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', key):
|
||||
raise ValueError(
|
||||
f"Invalid environment variable name '{key}'. Must start with letter or underscore, contain only alphanumeric characters and underscores"
|
||||
)
|
||||
|
||||
env[key] = value
|
||||
return env
|
||||
return v or {}
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Override equality operator to compare server configurations.
|
||||
|
||||
@@ -59,6 +189,12 @@ class MCPSHTTPServerConfig(BaseModel):
|
||||
url: str
|
||||
api_key: str | None = None
|
||||
|
||||
@field_validator('url')
|
||||
@classmethod
|
||||
def validate_url(cls, v: str) -> str:
|
||||
"""Validate URL format for MCP servers."""
|
||||
return _validate_mcp_url(v)
|
||||
|
||||
|
||||
class MCPConfig(BaseModel):
|
||||
"""Configuration for MCP (Message Control Protocol) settings.
|
||||
|
||||
@@ -5,7 +5,7 @@ import platform
|
||||
import sys
|
||||
from ast import literal_eval
|
||||
from types import UnionType
|
||||
from typing import MutableMapping, get_args, get_origin
|
||||
from typing import MutableMapping, get_args, get_origin, get_type_hints
|
||||
from uuid import uuid4
|
||||
|
||||
import toml
|
||||
@@ -154,8 +154,22 @@ def load_from_toml(cfg: OpenHandsConfig, toml_file: str = 'config.toml') -> None
|
||||
core_config = toml_config['core']
|
||||
|
||||
# Process core section if present
|
||||
cfg_type_hints = get_type_hints(cfg.__class__)
|
||||
for key, value in core_config.items():
|
||||
if hasattr(cfg, key):
|
||||
# Get expected type of the attribute
|
||||
expected_type = cfg_type_hints.get(key, None)
|
||||
|
||||
# Check if expected_type is a Union that includes SecretStr and value is str, e.g. search_api_key
|
||||
if expected_type:
|
||||
origin = get_origin(expected_type)
|
||||
args = get_args(expected_type)
|
||||
|
||||
if origin is UnionType and SecretStr in args and isinstance(value, str):
|
||||
value = SecretStr(value)
|
||||
elif expected_type is SecretStr and isinstance(value, str):
|
||||
value = SecretStr(value)
|
||||
|
||||
setattr(cfg, key, value)
|
||||
else:
|
||||
logger.openhands_logger.warning(
|
||||
|
||||
+20
-2
@@ -225,9 +225,27 @@ def create_controller(
|
||||
|
||||
|
||||
def generate_sid(config: OpenHandsConfig, session_name: str | None = None) -> str:
|
||||
"""Generate a session id based on the session name and the jwt secret."""
|
||||
"""Generate a session id based on the session name and the jwt secret.
|
||||
|
||||
The session ID is kept short to ensure Kubernetes resource names don't exceed
|
||||
the 63-character limit when prefixed with 'openhands-runtime-' (18 chars).
|
||||
Total length is limited to 32 characters to allow for suffixes like '-svc', '-pvc'.
|
||||
"""
|
||||
session_name = session_name or str(uuid.uuid4())
|
||||
jwt_secret = config.jwt_secret
|
||||
|
||||
hash_str = hashlib.sha256(f'{session_name}{jwt_secret}'.encode('utf-8')).hexdigest()
|
||||
return f'{session_name}-{hash_str[:16]}'
|
||||
|
||||
# Limit total session ID length to 32 characters for Kubernetes compatibility:
|
||||
# - 'openhands-runtime-' (18 chars) + session_id (32 chars) = 50 chars
|
||||
# - Leaves 13 chars for suffixes like '-svc' (4), '-pvc' (4), '-ingress-code' (13)
|
||||
if len(session_name) > 16:
|
||||
# If session_name is too long, use first 16 chars + 15-char hash for better readability
|
||||
# e.g., "vscode-extension" -> "vscode-extensio-{15-char-hash}"
|
||||
session_id = f'{session_name[:16]}-{hash_str[:15]}'
|
||||
else:
|
||||
# If session_name is short enough, use it + remaining space for hash
|
||||
remaining_chars = 32 - len(session_name) - 1 # -1 for the dash
|
||||
session_id = f'{session_name}-{hash_str[:remaining_chars]}'
|
||||
|
||||
return session_id[:32] # Ensure we never exceed 32 characters
|
||||
|
||||
@@ -5,4 +5,5 @@ If you simply answered a question, this final message should re-state the answer
|
||||
If you made changes, please first double-check the git diff, think carefully about the user's request(s), and check:
|
||||
1. whether the request has been completely addressed and all of the instructions have been followed faithfully (in checklist format if appropriate).
|
||||
2. whether the changes are concise (if there are any extraneous changes not important to addressing the user's request they should be reverted).
|
||||
3. focus only on summarizing new changes since your last summary, avoiding repetition of information already covered in previous summaries.
|
||||
If the request has been addressed and the changes are concise, then push your changes to the remote branch and send a final message summarizing the changes.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from openhands.mcp.client import MCPClient
|
||||
from openhands.mcp.error_collector import mcp_error_collector
|
||||
from openhands.mcp.tool import MCPClientTool
|
||||
from openhands.mcp.utils import (
|
||||
add_mcp_tools_to_agent,
|
||||
@@ -16,4 +17,5 @@ __all__ = [
|
||||
'fetch_mcp_tools_from_config',
|
||||
'call_tool_mcp',
|
||||
'add_mcp_tools_to_agent',
|
||||
'mcp_error_collector',
|
||||
]
|
||||
|
||||
+53
-4
@@ -1,13 +1,22 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
|
||||
from fastmcp.client.transports import (
|
||||
SSETransport,
|
||||
StdioTransport,
|
||||
StreamableHttpTransport,
|
||||
)
|
||||
from mcp import McpError
|
||||
from mcp.types import CallToolResult
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from openhands.core.config.mcp_config import MCPSHTTPServerConfig, MCPSSEServerConfig
|
||||
from openhands.core.config.mcp_config import (
|
||||
MCPSHTTPServerConfig,
|
||||
MCPSSEServerConfig,
|
||||
MCPStdioServerConfig,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.mcp.error_collector import mcp_error_collector
|
||||
from openhands.mcp.tool import MCPClientTool
|
||||
|
||||
|
||||
@@ -90,11 +99,51 @@ class MCPClient(BaseModel):
|
||||
|
||||
await self._initialize_and_list_tools()
|
||||
except McpError as e:
|
||||
logger.error(f'McpError connecting to {server_url}: {e}')
|
||||
error_msg = f'McpError connecting to {server_url}: {e}'
|
||||
logger.error(error_msg)
|
||||
mcp_error_collector.add_error(
|
||||
server_name=server_url,
|
||||
server_type='shttp'
|
||||
if isinstance(server, MCPSHTTPServerConfig)
|
||||
else 'sse',
|
||||
error_message=error_msg,
|
||||
exception_details=str(e),
|
||||
)
|
||||
raise # Re-raise the error
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'Error connecting to {server_url}: {e}')
|
||||
error_msg = f'Error connecting to {server_url}: {e}'
|
||||
logger.error(error_msg)
|
||||
mcp_error_collector.add_error(
|
||||
server_name=server_url,
|
||||
server_type='shttp'
|
||||
if isinstance(server, MCPSHTTPServerConfig)
|
||||
else 'sse',
|
||||
error_message=error_msg,
|
||||
exception_details=str(e),
|
||||
)
|
||||
raise
|
||||
|
||||
async def connect_stdio(self, server: MCPStdioServerConfig, timeout: float = 30.0):
|
||||
"""Connect to MCP server using stdio transport"""
|
||||
try:
|
||||
transport = StdioTransport(
|
||||
command=server.command, args=server.args or [], env=server.env
|
||||
)
|
||||
self.client = Client(transport, timeout=timeout)
|
||||
await self._initialize_and_list_tools()
|
||||
except Exception as e:
|
||||
server_name = getattr(
|
||||
server, 'name', f'{server.command} {" ".join(server.args or [])}'
|
||||
)
|
||||
error_msg = f'Failed to connect to stdio server {server_name}: {e}'
|
||||
logger.error(error_msg)
|
||||
mcp_error_collector.add_error(
|
||||
server_name=server_name,
|
||||
server_type='stdio',
|
||||
error_message=error_msg,
|
||||
exception_details=str(e),
|
||||
)
|
||||
raise
|
||||
|
||||
async def call_tool(self, tool_name: str, args: dict) -> CallToolResult:
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"""MCP Error Collector for capturing and storing MCP-related errors during startup."""
|
||||
|
||||
import threading
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class MCPError:
|
||||
"""Represents an MCP-related error."""
|
||||
|
||||
timestamp: float
|
||||
server_name: str
|
||||
server_type: str # 'stdio', 'sse', 'shttp'
|
||||
error_message: str
|
||||
exception_details: str | None = None
|
||||
|
||||
|
||||
class MCPErrorCollector:
|
||||
"""Thread-safe collector for MCP errors during startup."""
|
||||
|
||||
def __init__(self):
|
||||
self._errors: list[MCPError] = []
|
||||
self._lock = threading.Lock()
|
||||
self._collection_enabled = True
|
||||
|
||||
def add_error(
|
||||
self,
|
||||
server_name: str,
|
||||
server_type: str,
|
||||
error_message: str,
|
||||
exception_details: str | None = None,
|
||||
) -> None:
|
||||
"""Add an MCP error to the collection."""
|
||||
if not self._collection_enabled:
|
||||
return
|
||||
|
||||
with self._lock:
|
||||
error = MCPError(
|
||||
timestamp=time.time(),
|
||||
server_name=server_name,
|
||||
server_type=server_type,
|
||||
error_message=error_message,
|
||||
exception_details=exception_details,
|
||||
)
|
||||
self._errors.append(error)
|
||||
|
||||
def get_errors(self) -> list[MCPError]:
|
||||
"""Get a copy of all collected errors."""
|
||||
with self._lock:
|
||||
return self._errors.copy()
|
||||
|
||||
def has_errors(self) -> bool:
|
||||
"""Check if there are any collected errors."""
|
||||
with self._lock:
|
||||
return len(self._errors) > 0
|
||||
|
||||
def clear_errors(self) -> None:
|
||||
"""Clear all collected errors."""
|
||||
with self._lock:
|
||||
self._errors.clear()
|
||||
|
||||
def disable_collection(self) -> None:
|
||||
"""Disable error collection (useful after startup)."""
|
||||
self._collection_enabled = False
|
||||
|
||||
def enable_collection(self) -> None:
|
||||
"""Enable error collection."""
|
||||
self._collection_enabled = True
|
||||
|
||||
def get_error_count(self) -> int:
|
||||
"""Get the number of collected errors."""
|
||||
with self._lock:
|
||||
return len(self._errors)
|
||||
|
||||
|
||||
# Global instance for collecting MCP errors
|
||||
mcp_error_collector = MCPErrorCollector()
|
||||
+55
-8
@@ -11,14 +11,17 @@ from openhands.core.config.mcp_config import (
|
||||
MCPConfig,
|
||||
MCPSHTTPServerConfig,
|
||||
MCPSSEServerConfig,
|
||||
MCPStdioServerConfig,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.events.action.mcp import MCPAction
|
||||
from openhands.events.observation.mcp import MCPObservation
|
||||
from openhands.events.observation.observation import Observation
|
||||
from openhands.mcp.client import MCPClient
|
||||
from openhands.mcp.error_collector import mcp_error_collector
|
||||
from openhands.memory.memory import Memory
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.runtime.impl.cli.cli_runtime import CLIRuntime
|
||||
|
||||
|
||||
def convert_mcp_clients_to_tools(mcp_clients: list[MCPClient] | None) -> list[dict]:
|
||||
@@ -45,7 +48,14 @@ def convert_mcp_clients_to_tools(mcp_clients: list[MCPClient] | None) -> list[di
|
||||
mcp_tools = tool.to_param()
|
||||
all_mcp_tools.append(mcp_tools)
|
||||
except Exception as e:
|
||||
logger.error(f'Error in convert_mcp_clients_to_tools: {e}')
|
||||
error_msg = f'Error in convert_mcp_clients_to_tools: {e}'
|
||||
logger.error(error_msg)
|
||||
mcp_error_collector.add_error(
|
||||
server_name='general',
|
||||
server_type='conversion',
|
||||
error_message=error_msg,
|
||||
exception_details=str(e),
|
||||
)
|
||||
return []
|
||||
return all_mcp_tools
|
||||
|
||||
@@ -54,6 +64,7 @@ async def create_mcp_clients(
|
||||
sse_servers: list[MCPSSEServerConfig],
|
||||
shttp_servers: list[MCPSHTTPServerConfig],
|
||||
conversation_id: str | None = None,
|
||||
stdio_servers: list[MCPStdioServerConfig] | None = None,
|
||||
) -> list[MCPClient]:
|
||||
import sys
|
||||
|
||||
@@ -64,9 +75,13 @@ async def create_mcp_clients(
|
||||
)
|
||||
return []
|
||||
|
||||
servers: list[MCPSSEServerConfig | MCPSHTTPServerConfig] = [
|
||||
if stdio_servers is None:
|
||||
stdio_servers = []
|
||||
|
||||
servers: list[MCPSSEServerConfig | MCPSHTTPServerConfig | MCPStdioServerConfig] = [
|
||||
*sse_servers,
|
||||
*shttp_servers,
|
||||
*stdio_servers,
|
||||
]
|
||||
|
||||
if not servers:
|
||||
@@ -75,6 +90,17 @@ async def create_mcp_clients(
|
||||
mcp_clients = []
|
||||
|
||||
for server in servers:
|
||||
if isinstance(server, MCPStdioServerConfig):
|
||||
logger.info(f'Initializing MCP agent for {server} with stdio connection...')
|
||||
client = MCPClient()
|
||||
try:
|
||||
await client.connect_stdio(server)
|
||||
mcp_clients.append(client)
|
||||
except Exception as e:
|
||||
# Error is already logged and collected in client.connect_stdio()
|
||||
logger.error(f'Failed to connect to {server}: {str(e)}', exc_info=True)
|
||||
continue
|
||||
|
||||
is_shttp = isinstance(server, MCPSHTTPServerConfig)
|
||||
connection_type = 'SHTTP' if is_shttp else 'SSE'
|
||||
logger.info(
|
||||
@@ -89,13 +115,14 @@ async def create_mcp_clients(
|
||||
mcp_clients.append(client)
|
||||
|
||||
except Exception as e:
|
||||
# Error is already logged and collected in client.connect_http()
|
||||
logger.error(f'Failed to connect to {server}: {str(e)}', exc_info=True)
|
||||
|
||||
return mcp_clients
|
||||
|
||||
|
||||
async def fetch_mcp_tools_from_config(
|
||||
mcp_config: MCPConfig, conversation_id: str | None = None
|
||||
mcp_config: MCPConfig, conversation_id: str | None = None, use_stdio: bool = False
|
||||
) -> list[dict]:
|
||||
"""
|
||||
Retrieves the list of MCP tools from the MCP clients.
|
||||
@@ -103,6 +130,7 @@ async def fetch_mcp_tools_from_config(
|
||||
Args:
|
||||
mcp_config: The MCP configuration
|
||||
conversation_id: Optional conversation ID to associate with the MCP clients
|
||||
use_stdio: Whether to use stdio servers for MCP clients, set to True when running from a CLI runtime
|
||||
|
||||
Returns:
|
||||
A list of tool dictionaries. Returns an empty list if no connections could be established.
|
||||
@@ -120,7 +148,10 @@ async def fetch_mcp_tools_from_config(
|
||||
logger.debug(f'Creating MCP clients with config: {mcp_config}')
|
||||
# Create clients - this will fetch tools but not maintain active connections
|
||||
mcp_clients = await create_mcp_clients(
|
||||
mcp_config.sse_servers, mcp_config.shttp_servers, conversation_id
|
||||
mcp_config.sse_servers,
|
||||
mcp_config.shttp_servers,
|
||||
conversation_id,
|
||||
mcp_config.stdio_servers if use_stdio else [],
|
||||
)
|
||||
|
||||
if not mcp_clients:
|
||||
@@ -131,7 +162,14 @@ async def fetch_mcp_tools_from_config(
|
||||
mcp_tools = convert_mcp_clients_to_tools(mcp_clients)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'Error fetching MCP tools: {str(e)}')
|
||||
error_msg = f'Error fetching MCP tools: {str(e)}'
|
||||
logger.error(error_msg)
|
||||
mcp_error_collector.add_error(
|
||||
server_name='general',
|
||||
server_type='fetch',
|
||||
error_message=error_msg,
|
||||
exception_details=str(e),
|
||||
)
|
||||
return []
|
||||
|
||||
logger.debug(f'MCP tools: {mcp_tools}')
|
||||
@@ -200,7 +238,9 @@ async def call_tool_mcp(mcp_clients: list[MCPClient], action: MCPAction) -> Obse
|
||||
)
|
||||
|
||||
|
||||
async def add_mcp_tools_to_agent(agent: 'Agent', runtime: Runtime, memory: 'Memory'):
|
||||
async def add_mcp_tools_to_agent(
|
||||
agent: 'Agent', runtime: Runtime, memory: 'Memory'
|
||||
) -> MCPConfig:
|
||||
"""
|
||||
Add MCP tools to an agent.
|
||||
"""
|
||||
@@ -231,13 +271,18 @@ async def add_mcp_tools_to_agent(agent: 'Agent', runtime: Runtime, memory: 'Memo
|
||||
# Check if this stdio server is already in the config
|
||||
if stdio_server not in extra_stdio_servers:
|
||||
extra_stdio_servers.append(stdio_server)
|
||||
logger.info(f'Added microagent stdio server: {stdio_server.name}')
|
||||
logger.warning(
|
||||
f'Added microagent stdio server: {stdio_server.name}'
|
||||
)
|
||||
|
||||
# Add the runtime as another MCP server
|
||||
updated_mcp_config = runtime.get_mcp_config(extra_stdio_servers)
|
||||
|
||||
# Fetch the MCP tools
|
||||
mcp_tools = await fetch_mcp_tools_from_config(updated_mcp_config)
|
||||
# Only use stdio if run from a CLI runtime
|
||||
mcp_tools = await fetch_mcp_tools_from_config(
|
||||
updated_mcp_config, use_stdio=isinstance(runtime, CLIRuntime)
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f'Loaded {len(mcp_tools)} MCP tools: {[tool["function"]["name"] for tool in mcp_tools]}'
|
||||
@@ -245,3 +290,5 @@ async def add_mcp_tools_to_agent(agent: 'Agent', runtime: Runtime, memory: 'Memo
|
||||
|
||||
# Set the MCP tools on the agent
|
||||
agent.set_mcp_tools(mcp_tools)
|
||||
|
||||
return updated_mcp_config
|
||||
|
||||
@@ -149,6 +149,7 @@ class IssueResolver:
|
||||
args.base_container_image,
|
||||
args.runtime_container_image,
|
||||
args.is_experimental,
|
||||
args.runtime,
|
||||
)
|
||||
|
||||
self.owner = owner
|
||||
@@ -182,9 +183,11 @@ class IssueResolver:
|
||||
base_container_image: str | None,
|
||||
runtime_container_image: str | None,
|
||||
is_experimental: bool,
|
||||
runtime: str | None = None,
|
||||
) -> OpenHandsConfig:
|
||||
config.default_agent = 'CodeActAgent'
|
||||
config.runtime = 'docker'
|
||||
# Use provided runtime or fallback to config value or default to 'docker'
|
||||
config.runtime = runtime or config.runtime or 'docker'
|
||||
config.max_budget_per_task = 4
|
||||
config.max_iterations = max_iterations
|
||||
|
||||
|
||||
@@ -45,6 +45,12 @@ def main() -> None:
|
||||
default=None,
|
||||
help='Container image to use.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--runtime',
|
||||
type=str,
|
||||
default=None,
|
||||
help='Runtime environment to use (default: docker).',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--max-iterations',
|
||||
type=int,
|
||||
|
||||
@@ -17,7 +17,9 @@ import httpx
|
||||
|
||||
from openhands.core.config import OpenHandsConfig, SandboxConfig
|
||||
from openhands.core.config.mcp_config import MCPConfig, MCPStdioServerConfig
|
||||
from openhands.core.exceptions import AgentRuntimeDisconnectedError
|
||||
from openhands.core.exceptions import (
|
||||
AgentRuntimeDisconnectedError,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.events import EventSource, EventStream, EventStreamSubscriber
|
||||
from openhands.events.action import (
|
||||
@@ -49,6 +51,7 @@ from openhands.integrations.provider import (
|
||||
ProviderHandler,
|
||||
ProviderType,
|
||||
)
|
||||
from openhands.integrations.service_types import AuthenticationError
|
||||
from openhands.microagent import (
|
||||
BaseMicroagent,
|
||||
load_microagents_from_dir,
|
||||
@@ -339,10 +342,19 @@ class Runtime(FileEditRuntimeMixin):
|
||||
observation: Observation = await self.call_tool_mcp(event)
|
||||
else:
|
||||
observation = await call_sync_from_async(self.run_action, event)
|
||||
except PermissionError as e:
|
||||
# Handle PermissionError specially - convert to ErrorObservation
|
||||
# so the agent can receive feedback and continue execution
|
||||
observation = ErrorObservation(content=str(e))
|
||||
except (httpx.NetworkError, AgentRuntimeDisconnectedError) as e:
|
||||
runtime_status = RuntimeStatus.ERROR_RUNTIME_DISCONNECTED
|
||||
error_message = f'{type(e).__name__}: {str(e)}'
|
||||
self.log('error', f'Unexpected error while running action: {error_message}')
|
||||
self.log('error', f'Problematic action: {str(event)}')
|
||||
self.set_runtime_status(runtime_status, error_message)
|
||||
return
|
||||
except Exception as e:
|
||||
runtime_status = RuntimeStatus.ERROR
|
||||
if isinstance(e, (httpx.NetworkError, AgentRuntimeDisconnectedError)):
|
||||
runtime_status = RuntimeStatus.ERROR_RUNTIME_DISCONNECTED
|
||||
error_message = f'{type(e).__name__}: {str(e)}'
|
||||
self.log('error', f'Unexpected error while running action: {error_message}')
|
||||
self.log('error', f'Problematic action: {str(event)}')
|
||||
@@ -701,12 +713,18 @@ fi
|
||||
GENERAL_TIMEOUT,
|
||||
org_openhands_repo,
|
||||
)
|
||||
except AuthenticationError as e:
|
||||
self.log(
|
||||
'debug',
|
||||
f'org-level microagent directory {org_openhands_repo} not found: {str(e)}',
|
||||
)
|
||||
raise
|
||||
except Exception as e:
|
||||
self.log(
|
||||
'error',
|
||||
'debug',
|
||||
f'Failed to get authenticated URL for {org_openhands_repo}: {str(e)}',
|
||||
)
|
||||
raise Exception(str(e))
|
||||
raise
|
||||
|
||||
clone_cmd = (
|
||||
f'GIT_TERMINAL_PROMPT=0 git clone --depth 1 {remote_url} {org_repo_dir}'
|
||||
@@ -762,6 +780,11 @@ fi
|
||||
f'Clone command output: {clone_error_msg}',
|
||||
)
|
||||
|
||||
except AuthenticationError as e:
|
||||
self.log(
|
||||
'debug',
|
||||
f'org-level microagent directory {org_openhands_repo} not found: {str(e)}',
|
||||
)
|
||||
except Exception as e:
|
||||
self.log(
|
||||
'debug',
|
||||
|
||||
@@ -25,7 +25,6 @@ from pydantic import SecretStr
|
||||
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.config.mcp_config import MCPConfig, MCPStdioServerConfig
|
||||
from openhands.core.exceptions import LLMMalformedActionError
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.events import EventStream
|
||||
from openhands.events.action import (
|
||||
@@ -508,7 +507,7 @@ class CLIRuntime(Runtime):
|
||||
)
|
||||
elif filename.startswith('/'):
|
||||
if not filename.startswith(self._workspace_path):
|
||||
raise LLMMalformedActionError(
|
||||
raise PermissionError(
|
||||
f'Invalid path: {filename}. You can only work with files in {self._workspace_path}.'
|
||||
)
|
||||
actual_filename = filename
|
||||
@@ -520,7 +519,7 @@ class CLIRuntime(Runtime):
|
||||
|
||||
# Check if the resolved path is still within the workspace
|
||||
if not resolved_path.startswith(self._workspace_path):
|
||||
raise LLMMalformedActionError(
|
||||
raise PermissionError(
|
||||
f'Invalid path traversal: {filename}. Path resolves outside the workspace. Resolved: {resolved_path}, Workspace: {self._workspace_path}'
|
||||
)
|
||||
|
||||
@@ -689,8 +688,69 @@ class CLIRuntime(Runtime):
|
||||
)
|
||||
|
||||
async def call_tool_mcp(self, action: MCPAction) -> Observation:
|
||||
"""Not implemented for CLI runtime."""
|
||||
return ErrorObservation('MCP functionality is not implemented in CLIRuntime')
|
||||
"""Execute an MCP tool action in CLI runtime.
|
||||
|
||||
Args:
|
||||
action: The MCP action to execute
|
||||
|
||||
Returns:
|
||||
Observation: The result of the MCP tool execution
|
||||
"""
|
||||
# Check if we're on Windows - MCP is disabled on Windows
|
||||
if sys.platform == 'win32':
|
||||
self.log('info', 'MCP functionality is disabled on Windows')
|
||||
return ErrorObservation('MCP functionality is not available on Windows')
|
||||
|
||||
# Import here to avoid circular imports
|
||||
from openhands.mcp.utils import call_tool_mcp as call_tool_mcp_handler
|
||||
from openhands.mcp.utils import create_mcp_clients
|
||||
|
||||
try:
|
||||
# Get the MCP config for this runtime
|
||||
mcp_config = self.get_mcp_config()
|
||||
|
||||
if (
|
||||
not mcp_config.sse_servers
|
||||
and not mcp_config.shttp_servers
|
||||
and not mcp_config.stdio_servers
|
||||
):
|
||||
self.log('warning', 'No MCP servers configured')
|
||||
return ErrorObservation('No MCP servers configured')
|
||||
|
||||
self.log(
|
||||
'debug',
|
||||
f'Creating MCP clients for action {action.name} with servers: '
|
||||
f'SSE={len(mcp_config.sse_servers)}, SHTTP={len(mcp_config.shttp_servers)}, '
|
||||
f'stdio={len(mcp_config.stdio_servers)}',
|
||||
)
|
||||
|
||||
# Create clients for this specific operation
|
||||
mcp_clients = await create_mcp_clients(
|
||||
mcp_config.sse_servers,
|
||||
mcp_config.shttp_servers,
|
||||
self.sid,
|
||||
mcp_config.stdio_servers,
|
||||
)
|
||||
|
||||
if not mcp_clients:
|
||||
self.log('warning', 'No MCP clients could be created')
|
||||
return ErrorObservation(
|
||||
'No MCP clients could be created - check server configurations'
|
||||
)
|
||||
|
||||
# Call the tool and return the result
|
||||
self.log(
|
||||
'debug',
|
||||
f'Executing MCP tool: {action.name} with arguments: {action.arguments}',
|
||||
)
|
||||
result = await call_tool_mcp_handler(mcp_clients, action)
|
||||
self.log('debug', f'MCP tool {action.name} executed successfully')
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f'Error executing MCP tool {action.name}: {str(e)}'
|
||||
self.log('error', error_msg)
|
||||
return ErrorObservation(error_msg)
|
||||
|
||||
@property
|
||||
def workspace_root(self) -> Path:
|
||||
@@ -869,8 +929,40 @@ class CLIRuntime(Runtime):
|
||||
def get_mcp_config(
|
||||
self, extra_stdio_servers: list[MCPStdioServerConfig] | None = None
|
||||
) -> MCPConfig:
|
||||
# TODO: Load MCP config from a local file
|
||||
return MCPConfig()
|
||||
"""Get MCP configuration for CLI runtime.
|
||||
|
||||
Args:
|
||||
extra_stdio_servers: Additional stdio servers to include in the config
|
||||
|
||||
Returns:
|
||||
MCPConfig: The MCP configuration with stdio servers and any configured SSE/SHTTP servers
|
||||
"""
|
||||
# Check if we're on Windows - MCP is disabled on Windows
|
||||
if sys.platform == 'win32':
|
||||
self.log('debug', 'MCP is disabled on Windows, returning empty config')
|
||||
return MCPConfig(sse_servers=[], stdio_servers=[], shttp_servers=[])
|
||||
|
||||
# Note: we update the self.config.mcp directly for CLI runtime, which is different from other runtimes.
|
||||
mcp_config = self.config.mcp
|
||||
|
||||
# Add any extra stdio servers
|
||||
if extra_stdio_servers:
|
||||
current_stdio_servers = list(mcp_config.stdio_servers)
|
||||
for extra_server in extra_stdio_servers:
|
||||
# Check if this stdio server is already in the config
|
||||
if extra_server not in current_stdio_servers:
|
||||
current_stdio_servers.append(extra_server)
|
||||
self.log('info', f'Added extra stdio server: {extra_server.name}')
|
||||
mcp_config.stdio_servers = current_stdio_servers
|
||||
|
||||
self.log(
|
||||
'debug',
|
||||
f'CLI MCP config: {len(mcp_config.sse_servers)} SSE servers, '
|
||||
f'{len(mcp_config.stdio_servers)} stdio servers, '
|
||||
f'{len(mcp_config.shttp_servers)} SHTTP servers',
|
||||
)
|
||||
|
||||
return mcp_config
|
||||
|
||||
def subscribe_to_shell_stream(
|
||||
self, callback: Callable[[str], None] | None = None
|
||||
|
||||
@@ -40,7 +40,7 @@ Two configuration options are required to use the Kubernetes runtime:
|
||||
2. **Runtime Container Image**: Specify the container image to use for the runtime environment
|
||||
```toml
|
||||
[sandbox]
|
||||
runtime_container_image = "docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik"
|
||||
runtime_container_image = "docker.all-hands.dev/all-hands-ai/runtime:0.50-nikolaik"
|
||||
```
|
||||
|
||||
#### Additional Kubernetes Options
|
||||
|
||||
@@ -8,6 +8,7 @@ including initialization, configuration, and mounting to FastAPI applications.
|
||||
import logging
|
||||
from typing import Any, Optional
|
||||
|
||||
from anyio import get_cancelled_exc_class
|
||||
from fastapi import FastAPI
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.utilities.logging import get_logger as fastmcp_get_logger
|
||||
@@ -89,9 +90,29 @@ class MCPProxyManager:
|
||||
if not self.proxy:
|
||||
raise ValueError('FastMCP Proxy is not initialized')
|
||||
|
||||
def close_on_double_start(app):
|
||||
async def wrapped(scope, receive, send):
|
||||
start_sent = False
|
||||
|
||||
async def check_send(message):
|
||||
nonlocal start_sent
|
||||
if message['type'] == 'http.response.start':
|
||||
if start_sent:
|
||||
raise get_cancelled_exc_class()(
|
||||
'closed because of double http.response.start (mcp issue https://github.com/modelcontextprotocol/python-sdk/issues/883)'
|
||||
)
|
||||
start_sent = True
|
||||
await send(message)
|
||||
|
||||
await app(scope, receive, check_send)
|
||||
|
||||
return wrapped
|
||||
|
||||
# Get the SSE app
|
||||
# mcp_app = self.proxy.http_app(path='/shttp')
|
||||
mcp_app = self.proxy.http_app(path='/sse', transport='sse')
|
||||
mcp_app = close_on_double_start(
|
||||
self.proxy.http_app(path='/sse', transport='sse')
|
||||
)
|
||||
app.mount('/mcp', mcp_app)
|
||||
|
||||
# Remove any existing mounts at root path
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
@dataclass
|
||||
class CommandResult:
|
||||
"""Represents the result of a shell command execution.
|
||||
"""
|
||||
Represents the result of a shell command execution.
|
||||
|
||||
Attributes:
|
||||
content (str): The output content of the command.
|
||||
@@ -16,7 +18,9 @@ class CommandResult:
|
||||
|
||||
|
||||
class GitHandler:
|
||||
"""A handler for executing Git-related operations via shell commands."""
|
||||
"""
|
||||
A handler for executing Git-related operations via shell commands.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -26,7 +30,8 @@ class GitHandler:
|
||||
self.cwd: str | None = None
|
||||
|
||||
def set_cwd(self, cwd: str) -> None:
|
||||
"""Sets the current working directory for Git operations.
|
||||
"""
|
||||
Sets the current working directory for Git operations.
|
||||
|
||||
Args:
|
||||
cwd (str): The directory path.
|
||||
@@ -34,7 +39,8 @@ class GitHandler:
|
||||
self.cwd = cwd
|
||||
|
||||
def _is_git_repo(self) -> bool:
|
||||
"""Checks if the current directory is a Git repository.
|
||||
"""
|
||||
Checks if the current directory is a Git repository.
|
||||
|
||||
Returns:
|
||||
bool: True if inside a Git repository, otherwise False.
|
||||
@@ -44,7 +50,8 @@ class GitHandler:
|
||||
return output.content.strip() == 'true'
|
||||
|
||||
def _get_current_file_content(self, file_path: str) -> str:
|
||||
"""Retrieves the current content of a given file.
|
||||
"""
|
||||
Retrieves the current content of a given file.
|
||||
|
||||
Args:
|
||||
file_path (str): Path to the file.
|
||||
@@ -56,7 +63,8 @@ class GitHandler:
|
||||
return output.content
|
||||
|
||||
def _verify_ref_exists(self, ref: str) -> bool:
|
||||
"""Verifies whether a specific Git reference exists.
|
||||
"""
|
||||
Verifies whether a specific Git reference exists.
|
||||
|
||||
Args:
|
||||
ref (str): The Git reference to check.
|
||||
@@ -68,107 +76,10 @@ class GitHandler:
|
||||
output = self.execute(cmd, self.cwd)
|
||||
return output.exit_code == 0
|
||||
|
||||
def _is_ahead_of_remote_branch(self, remote_branch: str) -> bool:
|
||||
"""Checks if the current branch is ahead of the specified remote branch.
|
||||
|
||||
Args:
|
||||
remote_branch (str): The remote branch reference (e.g., 'origin/feature-branch').
|
||||
|
||||
Returns:
|
||||
bool: True if current branch is ahead, False otherwise.
|
||||
"""
|
||||
cmd = f'git --no-pager rev-list --count {remote_branch}..HEAD'
|
||||
output = self.execute(cmd, self.cwd)
|
||||
if output.exit_code != 0:
|
||||
return False
|
||||
return int(output.content.strip()) > 0
|
||||
|
||||
def _includes_merged_main_commits(self, remote_branch: str, default_branch: str) -> bool:
|
||||
"""Checks if the local branch includes commits that were merged from the default branch.
|
||||
|
||||
Since the remote branch was last updated.
|
||||
|
||||
Args:
|
||||
remote_branch (str): The remote branch reference (e.g., 'origin/feature-branch').
|
||||
default_branch (str): The default branch name (e.g., 'main').
|
||||
|
||||
Returns:
|
||||
bool: True if merged main commits are included in the diff.
|
||||
"""
|
||||
# Get commits that are in HEAD but not in remote_branch
|
||||
cmd = f'git --no-pager log --oneline {remote_branch}..HEAD'
|
||||
output = self.execute(cmd, self.cwd)
|
||||
if output.exit_code != 0:
|
||||
return False
|
||||
|
||||
local_commits = output.content.strip().splitlines()
|
||||
if not local_commits:
|
||||
return False
|
||||
|
||||
# Get commits that are in origin/default_branch but not in remote_branch
|
||||
origin_default = f'origin/{default_branch}'
|
||||
if not self._verify_ref_exists(origin_default):
|
||||
return False
|
||||
|
||||
cmd = f'git --no-pager log --oneline {remote_branch}..{origin_default}'
|
||||
output = self.execute(cmd, self.cwd)
|
||||
if output.exit_code != 0:
|
||||
return False
|
||||
|
||||
main_commits = output.content.strip().splitlines()
|
||||
if not main_commits:
|
||||
return False
|
||||
|
||||
# Extract commit hashes from both lists
|
||||
local_hashes = {line.split()[0] for line in local_commits if line.strip()}
|
||||
main_hashes = {line.split()[0] for line in main_commits if line.strip()}
|
||||
|
||||
# If there's significant overlap, we likely have merged main commits
|
||||
overlap = local_hashes.intersection(main_hashes)
|
||||
return len(overlap) >= min(2, len(main_hashes) // 2)
|
||||
|
||||
def _get_valid_ref(self) -> str | None:
|
||||
"""Determines a valid Git reference for comparison using a hybrid approach.
|
||||
|
||||
- Uses origin/current_branch when it's the best representation of push status
|
||||
- Falls back to merge-base when origin/current_branch includes merged main commits
|
||||
|
||||
Returns:
|
||||
str | None: A valid Git reference or None if no valid reference is found.
|
||||
"""
|
||||
current_branch = self._get_current_branch()
|
||||
default_branch = self._get_default_branch()
|
||||
|
||||
ref_current_branch = f'origin/{current_branch}'
|
||||
ref_non_default_branch = f'$(git --no-pager merge-base HEAD "$(git --no-pager rev-parse --abbrev-ref origin/{default_branch})")'
|
||||
ref_default_branch = 'origin/' + default_branch
|
||||
ref_new_repo = '$(git --no-pager rev-parse --verify 4b825dc642cb6eb9a060e54bf8d69288fbee4904)' # compares with empty tree
|
||||
|
||||
# Hybrid logic: check if origin/current_branch exists and causes pollution
|
||||
if self._verify_ref_exists(ref_current_branch):
|
||||
# If we're ahead of remote and it includes merged main commits, use merge-base instead
|
||||
if (self._is_ahead_of_remote_branch(ref_current_branch) and
|
||||
self._includes_merged_main_commits(ref_current_branch, default_branch)):
|
||||
# Try merge-base first to avoid pollution
|
||||
if self._verify_ref_exists(ref_non_default_branch):
|
||||
return ref_non_default_branch
|
||||
# Otherwise use origin/current_branch for normal push workflow
|
||||
return ref_current_branch
|
||||
|
||||
# Fallback to original logic
|
||||
refs = [
|
||||
ref_non_default_branch,
|
||||
ref_default_branch,
|
||||
ref_new_repo,
|
||||
]
|
||||
for ref in refs:
|
||||
if self._verify_ref_exists(ref):
|
||||
return ref
|
||||
|
||||
return None
|
||||
|
||||
def _get_ref_content(self, file_path: str) -> str:
|
||||
"""Retrieves the content of a file from a valid Git reference.
|
||||
"""
|
||||
Retrieves the content of a file from a valid Git reference.
|
||||
Finds the git repository closest to the file in the tree and executes the command in that context.
|
||||
|
||||
Args:
|
||||
file_path (str): The file path in the repository.
|
||||
@@ -176,85 +87,169 @@ class GitHandler:
|
||||
Returns:
|
||||
str: The content of the file from the reference, or an empty string if unavailable.
|
||||
"""
|
||||
ref = self._get_valid_ref()
|
||||
if not ref:
|
||||
if not self.cwd:
|
||||
return ''
|
||||
|
||||
cmd = f'git --no-pager show {ref}:{file_path}'
|
||||
output = self.execute(cmd, self.cwd)
|
||||
return output.content if output.exit_code == 0 else ''
|
||||
unique_id = uuid4().hex
|
||||
|
||||
def _get_default_branch(self) -> str:
|
||||
"""Retrieves the primary Git branch name of the repository.
|
||||
# Single bash command that finds the closest git repository to the file and gets the ref content
|
||||
cmd = f"""bash -c '
|
||||
# Convert to absolute path
|
||||
file_path="$(realpath "{file_path}")"
|
||||
|
||||
Returns:
|
||||
str: The name of the primary branch.
|
||||
"""
|
||||
cmd = 'git --no-pager remote show origin | grep "HEAD branch"'
|
||||
output = self.execute(cmd, self.cwd)
|
||||
return output.content.split()[-1].strip()
|
||||
# Find the closest git repository by walking up the directory tree
|
||||
current_dir="$(dirname "$file_path")"
|
||||
git_repo_dir=""
|
||||
|
||||
def _get_current_branch(self) -> str:
|
||||
"""Retrieves the currently selected Git branch.
|
||||
while [[ "$current_dir" != "/" ]]; do
|
||||
if [[ -d "$current_dir/.git" ]] || git -C "$current_dir" rev-parse --git-dir >/dev/null 2>&1; then
|
||||
git_repo_dir="$current_dir"
|
||||
break
|
||||
fi
|
||||
current_dir="$(dirname "$current_dir")"
|
||||
done
|
||||
|
||||
Returns:
|
||||
str: The name of the current branch.
|
||||
"""
|
||||
cmd = 'git --no-pager rev-parse --abbrev-ref HEAD'
|
||||
output = self.execute(cmd, self.cwd)
|
||||
return output.content.strip()
|
||||
# If no git repository found, exit
|
||||
if [[ -z "$git_repo_dir" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
def _get_changed_files(self) -> list[str]:
|
||||
"""Retrieves a list of changed files compared to a valid Git reference.
|
||||
# Get the file path relative to the git repository root
|
||||
repo_root="$(cd "$git_repo_dir" && git rev-parse --show-toplevel)"
|
||||
relative_file_path="${{file_path#${{repo_root}}/}}"
|
||||
|
||||
Returns:
|
||||
list[str]: A list of changed file paths.
|
||||
"""
|
||||
ref = self._get_valid_ref()
|
||||
if not ref:
|
||||
return []
|
||||
# Function to get current branch
|
||||
get_current_branch() {{
|
||||
git -C "$git_repo_dir" rev-parse --abbrev-ref HEAD 2>/dev/null
|
||||
}}
|
||||
|
||||
diff_cmd = f'git --no-pager diff --name-status {ref}'
|
||||
output = self.execute(diff_cmd, self.cwd)
|
||||
if output.exit_code != 0:
|
||||
raise RuntimeError(
|
||||
f'Failed to get diff for ref {ref} in {self.cwd}. Command output: {output.content}'
|
||||
)
|
||||
return output.content.splitlines()
|
||||
# Function to get default branch
|
||||
get_default_branch() {{
|
||||
git -C "$git_repo_dir" remote show origin 2>/dev/null | grep "HEAD branch" | awk "{{print \\$NF}}" || echo "main"
|
||||
}}
|
||||
|
||||
def _get_untracked_files(self) -> list[dict[str, str]]:
|
||||
"""Retrieves a list of untracked files in the repository. This is useful for detecting new files.
|
||||
# Function to verify if a ref exists
|
||||
verify_ref_exists() {{
|
||||
git -C "$git_repo_dir" rev-parse --verify "$1" >/dev/null 2>&1
|
||||
}}
|
||||
|
||||
Returns:
|
||||
list[dict[str, str]]: A list of dictionaries containing file paths and statuses.
|
||||
"""
|
||||
cmd = 'git --no-pager ls-files --others --exclude-standard'
|
||||
output = self.execute(cmd, self.cwd)
|
||||
obs_list = output.content.splitlines()
|
||||
return (
|
||||
[{'status': 'A', 'path': path} for path in obs_list]
|
||||
if output.exit_code == 0
|
||||
else []
|
||||
)
|
||||
# Get valid reference for comparison
|
||||
current_branch="$(get_current_branch)"
|
||||
default_branch="$(get_default_branch)"
|
||||
|
||||
# Check if origin remote exists
|
||||
has_origin="$(git -C "$git_repo_dir" remote | grep -q "^origin$" && echo "true" || echo "false")"
|
||||
|
||||
if [[ "$has_origin" == "true" ]]; then
|
||||
ref_current_branch="origin/$current_branch"
|
||||
ref_non_default_branch="$(git -C "$git_repo_dir" merge-base HEAD "$(git -C "$git_repo_dir" rev-parse --abbrev-ref origin/$default_branch)" 2>/dev/null || echo "")"
|
||||
ref_default_branch="origin/$default_branch"
|
||||
else
|
||||
# For repositories without origin, try HEAD~1 (previous commit) or empty tree
|
||||
ref_current_branch="HEAD~1"
|
||||
ref_non_default_branch=""
|
||||
ref_default_branch=""
|
||||
fi
|
||||
ref_new_repo="$(git -C "$git_repo_dir" rev-parse --verify 4b825dc642cb6eb9a060e54bf8d69288fbee4904 2>/dev/null || echo "")" # empty tree
|
||||
|
||||
# Try refs in order of preference
|
||||
valid_ref=""
|
||||
for ref in "$ref_current_branch" "$ref_non_default_branch" "$ref_default_branch" "$ref_new_repo"; do
|
||||
if [[ -n "$ref" ]] && verify_ref_exists "$ref"; then
|
||||
valid_ref="$ref"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# If no valid ref found, exit
|
||||
if [[ -z "$valid_ref" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the file content from the reference
|
||||
git -C "$git_repo_dir" show "$valid_ref:$relative_file_path" 2>/dev/null || exit 1
|
||||
|
||||
# {unique_id}'"""
|
||||
|
||||
result = self.execute(cmd, self.cwd)
|
||||
|
||||
if result.exit_code != 0:
|
||||
return ''
|
||||
|
||||
# TODO: The command echoes the bash script. Why?
|
||||
content = result.content.split(f'{unique_id}')[-1]
|
||||
|
||||
return content
|
||||
|
||||
def get_git_changes(self) -> list[dict[str, str]] | None:
|
||||
"""Retrieves the list of changed files in the Git repository.
|
||||
"""
|
||||
Retrieves the list of changed files in Git repositories.
|
||||
Examines each direct subdirectory of the workspace directory looking for git repositories
|
||||
and returns the changes for each of these directories.
|
||||
Optimized to use a single git command per repository for maximum performance.
|
||||
|
||||
Returns:
|
||||
list[dict[str, str]] | None: A list of dictionaries containing file paths and statuses. None if not a git repository.
|
||||
list[dict[str, str]] | None: A list of dictionaries containing file paths and statuses. None if no git repositories found.
|
||||
"""
|
||||
if not self._is_git_repo():
|
||||
# If cwd is not set, return None
|
||||
if not self.cwd:
|
||||
return None
|
||||
|
||||
changes_list = self._get_changed_files()
|
||||
result = parse_git_changes(changes_list)
|
||||
# Single bash command that:
|
||||
# 1. Creates a list of directories to check (current dir + direct subdirectories)
|
||||
# 2. For each directory, checks if it's a git repo and gets status
|
||||
# 3. Outputs in format: REPO_PATH|STATUS|FILE_PATH
|
||||
cmd = """bash -c '
|
||||
{
|
||||
# Check current directory first
|
||||
echo "."
|
||||
# List direct subdirectories (excluding hidden ones)
|
||||
find . -maxdepth 1 -type d ! -name ".*" ! -name "." 2>/dev/null || true
|
||||
} | while IFS= read -r dir; do
|
||||
if [ -d "$dir/.git" ] || git -C "$dir" rev-parse --git-dir >/dev/null 2>&1; then
|
||||
# Get absolute path of the directory
|
||||
# Get git status for this repository
|
||||
git -C "$dir" status --porcelain -uall 2>/dev/null | while IFS= read -r line; do
|
||||
if [ -n "$line" ]; then
|
||||
# Extract status (first 2 chars) and file path (from char 3 onwards)
|
||||
status=$(echo "$line" | cut -c1-2)
|
||||
file_path=$(echo "$line" | cut -c4-)
|
||||
# Convert status codes to single character
|
||||
case "$status" in
|
||||
"M "*|" M") echo "$dir|M|$file_path" ;;
|
||||
"A "*|" A") echo "$dir|A|$file_path" ;;
|
||||
"D "*|" D") echo "$dir|D|$file_path" ;;
|
||||
"R "*|" R") echo "$dir|R|$file_path" ;;
|
||||
"C "*|" C") echo "$dir|C|$file_path" ;;
|
||||
"U "*|" U") echo "$dir|U|$file_path" ;;
|
||||
"??") echo "$dir|A|$file_path" ;;
|
||||
*) echo "$dir|M|$file_path" ;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
' """
|
||||
|
||||
# join with any untracked files
|
||||
result += self._get_untracked_files()
|
||||
return result
|
||||
result = self.execute(cmd.strip(), self.cwd)
|
||||
if result.exit_code != 0 or not result.content.strip():
|
||||
return None
|
||||
|
||||
# Parse the output
|
||||
changes = []
|
||||
for line in result.content.strip().split('\n'):
|
||||
if '|' in line:
|
||||
parts = line.split('|', 2)
|
||||
if len(parts) == 3:
|
||||
repo_path, status, file_path = parts
|
||||
file_path = f'{repo_path}/{file_path}'[2:]
|
||||
changes.append({'status': status, 'path': file_path})
|
||||
|
||||
return changes if changes else None
|
||||
|
||||
def get_git_diff(self, file_path: str) -> dict[str, str]:
|
||||
"""Retrieves the original and modified content of a file in the repository.
|
||||
"""
|
||||
Retrieves the original and modified content of a file in the repository.
|
||||
|
||||
Args:
|
||||
file_path (str): Path to the file.
|
||||
@@ -272,7 +267,8 @@ class GitHandler:
|
||||
|
||||
|
||||
def parse_git_changes(changes_list: list[str]) -> list[dict[str, str]]:
|
||||
"""Parses the list of changed files and extracts their statuses and paths.
|
||||
"""
|
||||
Parses the list of changed files and extracts their statuses and paths.
|
||||
|
||||
Args:
|
||||
changes_list (list[str]): List of changed file entries.
|
||||
|
||||
@@ -234,11 +234,7 @@ async def git_changes(
|
||||
) -> list[dict[str, str]] | JSONResponse:
|
||||
runtime: Runtime = conversation.runtime
|
||||
|
||||
cwd = await get_cwd(
|
||||
conversation_store,
|
||||
conversation.sid,
|
||||
runtime.config.workspace_mount_path_in_sandbox,
|
||||
)
|
||||
cwd = runtime.config.workspace_mount_path_in_sandbox
|
||||
logger.info(f'Getting git changes in {cwd}')
|
||||
|
||||
try:
|
||||
@@ -275,11 +271,7 @@ async def git_diff(
|
||||
) -> dict[str, Any] | JSONResponse:
|
||||
runtime: Runtime = conversation.runtime
|
||||
|
||||
cwd = await get_cwd(
|
||||
conversation_store,
|
||||
conversation.sid,
|
||||
runtime.config.workspace_mount_path_in_sandbox,
|
||||
)
|
||||
cwd = runtime.config.workspace_mount_path_in_sandbox
|
||||
|
||||
try:
|
||||
diff = await call_sync_from_async(runtime.get_git_diff, path, cwd)
|
||||
@@ -292,20 +284,6 @@ async def git_diff(
|
||||
)
|
||||
|
||||
|
||||
async def get_cwd(
|
||||
conversation_store: ConversationStore,
|
||||
conversation_id: str,
|
||||
workspace_mount_path_in_sandbox: str,
|
||||
) -> str:
|
||||
metadata = await conversation_store.get_metadata(conversation_id)
|
||||
cwd = workspace_mount_path_in_sandbox
|
||||
if metadata and metadata.selected_repository:
|
||||
repo_dir = metadata.selected_repository.split('/')[-1]
|
||||
cwd = os.path.join(cwd, repo_dir)
|
||||
|
||||
return cwd
|
||||
|
||||
|
||||
@app.post('/upload-files', response_model=POSTUploadFilesModel)
|
||||
async def upload_files(
|
||||
files: list[UploadFile],
|
||||
|
||||
@@ -36,7 +36,7 @@ In-memory storage keeps files in memory, which is useful for testing or temporar
|
||||
S3 storage uses Amazon S3 or compatible services for file storage.
|
||||
|
||||
**Environment Variables:**
|
||||
- The bucket name is specified by `file_store_path` in the configuration with a fallback to the `AWS_S3_BUCKET` enviroment variable.
|
||||
- The bucket name is specified by `file_store_path` in the configuration with a fallback to the `AWS_S3_BUCKET` environment variable.
|
||||
- `AWS_ACCESS_KEY_ID`: Your AWS access key
|
||||
- `AWS_SECRET_ACCESS_KEY`: Your AWS secret key
|
||||
- `AWS_S3_ENDPOINT`: Optional custom endpoint for S3-compatible services (Allows overriding the default)
|
||||
@@ -47,7 +47,7 @@ S3 storage uses Amazon S3 or compatible services for file storage.
|
||||
Google Cloud Storage uses Google Cloud Storage buckets for file storage.
|
||||
|
||||
**Environment Variables:**
|
||||
- The bucket name is specified by `file_store_path` in the configuration with a fallback to the `GOOGLE_CLOUD_BUCKET_NAME` enviroment variable.
|
||||
- The bucket name is specified by `file_store_path` in the configuration with a fallback to the `GOOGLE_CLOUD_BUCKET_NAME` environment variable.
|
||||
- `GOOGLE_APPLICATION_CREDENTIALS`: Path to Google Cloud credentials JSON file
|
||||
|
||||
## Webhook Protocol
|
||||
|
||||
@@ -39,7 +39,7 @@ class FileSecretsStore(SecretsStore):
|
||||
async def get_instance(
|
||||
cls, config: OpenHandsConfig, user_id: str | None
|
||||
) -> FileSecretsStore:
|
||||
file_store = file_store = get_file_store(
|
||||
file_store = get_file_store(
|
||||
config.file_store,
|
||||
config.file_store_path,
|
||||
config.file_store_web_hook_url,
|
||||
|
||||
@@ -33,7 +33,7 @@ class FileSettingsStore(SettingsStore):
|
||||
async def get_instance(
|
||||
cls, config: OpenHandsConfig, user_id: str | None
|
||||
) -> FileSettingsStore:
|
||||
file_store = file_store = get_file_store(
|
||||
file_store = get_file_store(
|
||||
config.file_store,
|
||||
config.file_store_path,
|
||||
config.file_store_web_hook_url,
|
||||
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "OpenHands",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
Generated
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "@openhands/types",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openhands/types",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "@openhands/types",
|
||||
"version": "0.1.0",
|
||||
"description": "Shared type definitions and utilities for OpenHands projects",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import { OpenHandsActionEvent } from "./base";
|
||||
import { ActionSecurityRisk } from "./security";
|
||||
|
||||
export interface UserMessageAction extends OpenHandsActionEvent<"message"> {
|
||||
source: "user";
|
||||
args: {
|
||||
content: string;
|
||||
image_urls: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface SystemMessageAction extends OpenHandsActionEvent<"system"> {
|
||||
source: "agent";
|
||||
args: {
|
||||
content: string;
|
||||
tools: Array<Record<string, unknown>> | null;
|
||||
openhands_version: string | null;
|
||||
agent_class: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CommandAction extends OpenHandsActionEvent<"run"> {
|
||||
source: "agent" | "user";
|
||||
args: {
|
||||
command: string;
|
||||
security_risk: ActionSecurityRisk;
|
||||
confirmation_state: "confirmed" | "rejected" | "awaiting_confirmation";
|
||||
thought: string;
|
||||
hidden?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AssistantMessageAction
|
||||
extends OpenHandsActionEvent<"message"> {
|
||||
source: "agent";
|
||||
args: {
|
||||
thought: string;
|
||||
image_urls: string[] | null;
|
||||
wait_for_response: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IPythonAction extends OpenHandsActionEvent<"run_ipython"> {
|
||||
source: "agent";
|
||||
args: {
|
||||
code: string;
|
||||
security_risk: ActionSecurityRisk;
|
||||
confirmation_state: "confirmed" | "rejected" | "awaiting_confirmation";
|
||||
kernel_init_code: string;
|
||||
thought: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ThinkAction extends OpenHandsActionEvent<"think"> {
|
||||
source: "agent";
|
||||
args: {
|
||||
thought: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FinishAction extends OpenHandsActionEvent<"finish"> {
|
||||
source: "agent";
|
||||
args: {
|
||||
final_thought: string;
|
||||
task_completed: "success" | "failure" | "partial";
|
||||
outputs: Record<string, unknown>;
|
||||
thought: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DelegateAction extends OpenHandsActionEvent<"delegate"> {
|
||||
source: "agent";
|
||||
timeout: number;
|
||||
args: {
|
||||
agent: "BrowsingAgent";
|
||||
inputs: Record<string, string>;
|
||||
thought: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BrowseAction extends OpenHandsActionEvent<"browse"> {
|
||||
source: "agent";
|
||||
args: {
|
||||
url: string;
|
||||
thought: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BrowseInteractiveAction
|
||||
extends OpenHandsActionEvent<"browse_interactive"> {
|
||||
source: "agent";
|
||||
timeout: number;
|
||||
args: {
|
||||
browser_actions: string;
|
||||
thought: string | null;
|
||||
browsergym_send_msg_to_user: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FileReadAction extends OpenHandsActionEvent<"read"> {
|
||||
source: "agent";
|
||||
args: {
|
||||
path: string;
|
||||
thought: string;
|
||||
security_risk: ActionSecurityRisk | null;
|
||||
impl_source?: string;
|
||||
view_range?: number[] | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FileWriteAction extends OpenHandsActionEvent<"write"> {
|
||||
source: "agent";
|
||||
args: {
|
||||
path: string;
|
||||
content: string;
|
||||
thought: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FileEditAction extends OpenHandsActionEvent<"edit"> {
|
||||
source: "agent";
|
||||
args: {
|
||||
path: string;
|
||||
command?: string;
|
||||
file_text?: string | null;
|
||||
view_range?: number[] | null;
|
||||
old_str?: string | null;
|
||||
new_str?: string | null;
|
||||
insert_line?: number | null;
|
||||
content?: string;
|
||||
start?: number;
|
||||
end?: number;
|
||||
thought: string;
|
||||
security_risk: ActionSecurityRisk | null;
|
||||
impl_source?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RejectAction extends OpenHandsActionEvent<"reject"> {
|
||||
source: "agent";
|
||||
args: {
|
||||
thought: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RecallAction extends OpenHandsActionEvent<"recall"> {
|
||||
source: "agent";
|
||||
args: {
|
||||
recall_type: "workspace_context" | "knowledge";
|
||||
query: string;
|
||||
thought: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MCPAction extends OpenHandsActionEvent<"call_tool_mcp"> {
|
||||
source: "agent";
|
||||
args: {
|
||||
name: string;
|
||||
arguments: Record<string, unknown>;
|
||||
thought?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type OpenHandsAction =
|
||||
| UserMessageAction
|
||||
| AssistantMessageAction
|
||||
| SystemMessageAction
|
||||
| CommandAction
|
||||
| IPythonAction
|
||||
| ThinkAction
|
||||
| FinishAction
|
||||
| DelegateAction
|
||||
| BrowseAction
|
||||
| BrowseInteractiveAction
|
||||
| FileReadAction
|
||||
| FileEditAction
|
||||
| FileWriteAction
|
||||
| RejectAction
|
||||
| RecallAction
|
||||
| MCPAction;
|
||||
@@ -0,0 +1,15 @@
|
||||
export enum AgentState {
|
||||
INIT = "INIT",
|
||||
RUNNING = "RUNNING",
|
||||
AWAITING_USER_INPUT = "AWAITING_USER_INPUT",
|
||||
PAUSED = "PAUSED",
|
||||
LOADING = "LOADING",
|
||||
STOPPED = "STOPPED",
|
||||
FINISHED = "FINISHED",
|
||||
REJECTED = "REJECTED",
|
||||
ERROR = "ERROR",
|
||||
AWAITING_USER_CONFIRMATION = "AWAITING_USER_CONFIRMATION",
|
||||
USER_CONFIRMED = "USER_CONFIRMED",
|
||||
USER_REJECTED = "USER_REJECTED",
|
||||
RATE_LIMITED = "RATE_LIMITED",
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
export type OpenHandsEventType =
|
||||
| "message"
|
||||
| "system"
|
||||
| "agent_state_changed"
|
||||
| "change_agent_state"
|
||||
| "run"
|
||||
| "read"
|
||||
| "write"
|
||||
| "edit"
|
||||
| "run_ipython"
|
||||
| "delegate"
|
||||
| "browse"
|
||||
| "browse_interactive"
|
||||
| "reject"
|
||||
| "think"
|
||||
| "finish"
|
||||
| "error"
|
||||
| "recall"
|
||||
| "mcp"
|
||||
| "call_tool_mcp"
|
||||
| "user_rejected";
|
||||
|
||||
export type OpenHandsSourceType = "agent" | "user" | "environment";
|
||||
|
||||
interface OpenHandsBaseEvent {
|
||||
id: number;
|
||||
source: OpenHandsSourceType;
|
||||
message: string;
|
||||
timestamp: string; // ISO 8601
|
||||
}
|
||||
|
||||
export interface OpenHandsActionEvent<T extends OpenHandsEventType>
|
||||
extends OpenHandsBaseEvent {
|
||||
action: T;
|
||||
args: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface OpenHandsObservationEvent<T extends OpenHandsEventType>
|
||||
extends OpenHandsBaseEvent {
|
||||
cause: number;
|
||||
observation: T;
|
||||
content: string;
|
||||
extras: Record<string, unknown>;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { OpenHandsParsedEvent } from ".";
|
||||
import {
|
||||
UserMessageAction,
|
||||
AssistantMessageAction,
|
||||
OpenHandsAction,
|
||||
SystemMessageAction,
|
||||
CommandAction,
|
||||
} from "./actions";
|
||||
import {
|
||||
AgentStateChangeObservation,
|
||||
CommandObservation,
|
||||
ErrorObservation,
|
||||
MCPObservation,
|
||||
OpenHandsObservation,
|
||||
} from "./observations";
|
||||
import { StatusUpdate } from "./variances";
|
||||
|
||||
export const isOpenHandsAction = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is OpenHandsAction => "action" in event;
|
||||
|
||||
export const isOpenHandsObservation = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is OpenHandsObservation => "observation" in event;
|
||||
|
||||
export const isUserMessage = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is UserMessageAction =>
|
||||
isOpenHandsAction(event) &&
|
||||
event.source === "user" &&
|
||||
event.action === "message";
|
||||
|
||||
export const isAssistantMessage = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is AssistantMessageAction =>
|
||||
isOpenHandsAction(event) &&
|
||||
event.source === "agent" &&
|
||||
(event.action === "message" || event.action === "finish");
|
||||
|
||||
export const isErrorObservation = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is ErrorObservation =>
|
||||
isOpenHandsObservation(event) && event.observation === "error";
|
||||
|
||||
export const isCommandAction = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is CommandAction => isOpenHandsAction(event) && event.action === "run";
|
||||
|
||||
export const isAgentStateChangeObservation = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is AgentStateChangeObservation =>
|
||||
isOpenHandsObservation(event) && event.observation === "agent_state_changed";
|
||||
|
||||
export const isCommandObservation = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is CommandObservation =>
|
||||
isOpenHandsObservation(event) && event.observation === "run";
|
||||
|
||||
export const isFinishAction = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is AssistantMessageAction =>
|
||||
isOpenHandsAction(event) && event.action === "finish";
|
||||
|
||||
export const isSystemMessage = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is SystemMessageAction =>
|
||||
isOpenHandsAction(event) && event.action === "system";
|
||||
|
||||
export const isRejectObservation = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is OpenHandsObservation =>
|
||||
isOpenHandsObservation(event) && event.observation === "user_rejected";
|
||||
|
||||
export const isMcpObservation = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is MCPObservation =>
|
||||
isOpenHandsObservation(event) && event.observation === "mcp";
|
||||
|
||||
export const isStatusUpdate = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is StatusUpdate =>
|
||||
"status_update" in event && "type" in event && "id" in event;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { OpenHandsAction } from "./actions";
|
||||
import { OpenHandsObservation } from "./observations";
|
||||
import { OpenHandsVariance } from "./variances";
|
||||
|
||||
export type OpenHandsParsedEvent =
|
||||
| OpenHandsAction
|
||||
| OpenHandsObservation
|
||||
| OpenHandsVariance;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user