Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +24 -0
- Dockerfile +56 -0
- README.md +559 -5
- __init__.py +1 -0
- agent/__init__.py +1 -0
- agent/hybrid_policy.py +245 -0
- agent/memory.py +178 -0
- agent/policy.py +340 -0
- agent/reflection.py +168 -0
- agent/train.py +508 -0
- assets/episode.png +0 -0
- assets/formulas-1.png +3 -0
- assets/formulas-2.png +0 -0
- assets/gs.png +0 -0
- assets/hybrid.png +3 -0
- assets/logo.png +0 -0
- assets/reflexion.png +3 -0
- assets/sys arch.png +3 -0
- baseline_agent.py +249 -0
- bedrock_model.py +167 -0
- check.sh +332 -0
- client.py +141 -0
- dashboard/README.md +327 -0
- dashboard/backend/main.py +473 -0
- dashboard/backend/requirements.txt +5 -0
- dashboard/frontend/index.html +13 -0
- dashboard/frontend/node_modules/@alloc/quick-lru/index.d.ts +128 -0
- dashboard/frontend/node_modules/@alloc/quick-lru/index.js +263 -0
- dashboard/frontend/node_modules/@alloc/quick-lru/license +9 -0
- dashboard/frontend/node_modules/@alloc/quick-lru/package.json +43 -0
- dashboard/frontend/node_modules/@alloc/quick-lru/readme.md +139 -0
- dashboard/frontend/node_modules/@babel/code-frame/LICENSE +22 -0
- dashboard/frontend/node_modules/@babel/code-frame/README.md +19 -0
- dashboard/frontend/node_modules/@babel/code-frame/lib/index.js +217 -0
- dashboard/frontend/node_modules/@babel/code-frame/lib/index.js.map +1 -0
- dashboard/frontend/node_modules/@babel/code-frame/package.json +32 -0
- dashboard/frontend/node_modules/@babel/compat-data/LICENSE +22 -0
- dashboard/frontend/node_modules/@babel/compat-data/README.md +19 -0
- dashboard/frontend/node_modules/@babel/compat-data/corejs2-built-ins.js +2 -0
- dashboard/frontend/node_modules/@babel/compat-data/corejs3-shipped-proposals.js +2 -0
- dashboard/frontend/node_modules/@babel/compat-data/data/corejs2-built-ins.json +2106 -0
- dashboard/frontend/node_modules/@babel/compat-data/data/corejs3-shipped-proposals.json +5 -0
- dashboard/frontend/node_modules/@babel/compat-data/data/native-modules.json +18 -0
- dashboard/frontend/node_modules/@babel/compat-data/data/overlapping-plugins.json +35 -0
- dashboard/frontend/node_modules/@babel/compat-data/data/plugin-bugfixes.json +203 -0
- dashboard/frontend/node_modules/@babel/compat-data/data/plugins.json +838 -0
- dashboard/frontend/node_modules/@babel/compat-data/native-modules.js +2 -0
- dashboard/frontend/node_modules/@babel/compat-data/overlapping-plugins.js +2 -0
- dashboard/frontend/node_modules/@babel/compat-data/package.json +40 -0
- dashboard/frontend/node_modules/@babel/compat-data/plugin-bugfixes.js +2 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,27 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
assets/formulas-1.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
assets/hybrid.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
assets/reflexion.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
assets/sys[[:space:]]arch.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
dashboard/frontend/node_modules/@esbuild/linux-x64/bin/esbuild filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
dashboard/frontend/node_modules/@rollup/rollup-linux-x64-gnu/rollup.linux-x64-gnu.node filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
dashboard/frontend/node_modules/esbuild/bin/esbuild filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
images/big.png filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
wheels/anyio-4.13.0-py3-none-any.whl filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
wheels/boto3-1.42.78-py3-none-any.whl filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
wheels/botocore-1.42.78-py3-none-any.whl filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
wheels/certifi-2026.2.25-py3-none-any.whl filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
wheels/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
wheels/click-8.3.1-py3-none-any.whl filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
wheels/fastapi-0.135.2-py3-none-any.whl filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
wheels/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
wheels/pydantic-2.12.5-py3-none-any.whl filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
wheels/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl filter=lfs diff=lfs merge=lfs -text
|
| 54 |
+
wheels/python_dateutil-2.9.0.post0-py2.py3-none-any.whl filter=lfs diff=lfs merge=lfs -text
|
| 55 |
+
wheels/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl filter=lfs diff=lfs merge=lfs -text
|
| 56 |
+
wheels/urllib3-2.6.3-py3-none-any.whl filter=lfs diff=lfs merge=lfs -text
|
| 57 |
+
wheels/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl filter=lfs diff=lfs merge=lfs -text
|
| 58 |
+
wheels/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl filter=lfs diff=lfs merge=lfs -text
|
| 59 |
+
wheels/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ── GraphStrike — OpenEnv Environment Server ─────────────────────────────────
|
| 2 |
+
# This Dockerfile runs the environment server (FastAPI + Gradio UI + episodes).
|
| 3 |
+
# No training loop, no AWS credentials required.
|
| 4 |
+
#
|
| 5 |
+
# Used by: openenv push / HuggingFace Spaces deployment
|
| 6 |
+
# Port: 7860 (HF Spaces default — configurable via PORT env var)
|
| 7 |
+
#
|
| 8 |
+
# For local training (Qwen3 + Reflexion + Hybrid Policy), use server/Dockerfile.
|
| 9 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 10 |
+
|
| 11 |
+
FROM python:3.12-slim
|
| 12 |
+
|
| 13 |
+
WORKDIR /app
|
| 14 |
+
|
| 15 |
+
# ── Install Python dependencies (network available on HF build workers) ───────
|
| 16 |
+
COPY requirements.txt .
|
| 17 |
+
RUN pip install --no-cache-dir \
|
| 18 |
+
fastapi \
|
| 19 |
+
"uvicorn[standard]" \
|
| 20 |
+
"pydantic>=2.6.0" \
|
| 21 |
+
requests \
|
| 22 |
+
"openenv-core>=0.2.0" \
|
| 23 |
+
"gradio>=4.0.0" \
|
| 24 |
+
"openai>=1.0.0"
|
| 25 |
+
|
| 26 |
+
# ── Copy source code ──────────────────────────────────────────────────────────
|
| 27 |
+
COPY . .
|
| 28 |
+
|
| 29 |
+
# ── Pre-generate all 150 episodes at build time (~1 second, deterministic) ───
|
| 30 |
+
# This bakes the episodes into the image so the server starts instantly.
|
| 31 |
+
RUN python server/generator.py
|
| 32 |
+
|
| 33 |
+
# ── Dirs for optional persistent data (mounted as volumes on local Docker) ───
|
| 34 |
+
RUN mkdir -p /app/memory /app/runs
|
| 35 |
+
|
| 36 |
+
# ── Runtime config ────────────────────────────────────────────────────────────
|
| 37 |
+
ENV PORT=7860
|
| 38 |
+
ENV AWS_DEFAULT_REGION=us-east-1
|
| 39 |
+
|
| 40 |
+
# HF Spaces expects the app on port 7860 (override via PORT env var)
|
| 41 |
+
EXPOSE 7860
|
| 42 |
+
|
| 43 |
+
# Server only — the training loop is NOT started here.
|
| 44 |
+
# Judges evaluate the environment via the API endpoints:
|
| 45 |
+
# GET /health → liveness check
|
| 46 |
+
# GET /tasks → available tasks + action schema
|
| 47 |
+
# POST /reset → start an episode
|
| 48 |
+
# POST /step → take an action
|
| 49 |
+
# GET /grader → get normalised score after SUBMIT
|
| 50 |
+
# POST /baseline → run the rule-based agent on all 3 tasks
|
| 51 |
+
ENV ENABLE_WEB_INTERFACE=true
|
| 52 |
+
CMD ["python", "-m", "uvicorn", "server.app:app", \
|
| 53 |
+
"--host", "0.0.0.0", \
|
| 54 |
+
"--port", "7860", \
|
| 55 |
+
"--workers", "1", \
|
| 56 |
+
"--log-level", "info"]
|
README.md
CHANGED
|
@@ -1,10 +1,564 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
|
|
|
| 7 |
pinned: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
|
|
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: GraphStrike
|
| 3 |
+
emoji: 🕵️
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
tags:
|
| 11 |
+
- reinforcement-learning
|
| 12 |
+
- social-network
|
| 13 |
+
- fraud-detection
|
| 14 |
+
- openenv
|
| 15 |
+
- llm-agent
|
| 16 |
+
base_path: /web
|
| 17 |
---
|
| 18 |
+
<br>
|
| 19 |
|
| 20 |
+
<p align="center">
|
| 21 |
+
<img src="assets/logo.png" width="600"/>
|
| 22 |
+
</p>
|
| 23 |
+
|
| 24 |
+
<br>
|
| 25 |
+
|
| 26 |
+
<p align="center">
|
| 27 |
+
<img src="https://img.shields.io/badge/Hugging%20Face-FFD21E?style=for-the-badge&logo=huggingface&logoColor=black"/>
|
| 28 |
+
<img src="https://img.shields.io/badge/HF%20Spaces-FFBF00?style=for-the-badge&logo=huggingface&logoColor=black"/>
|
| 29 |
+
<img src="https://img.shields.io/badge/FastAPI-009688?style=for-the-badge&logo=fastapi&logoColor=white"/>
|
| 30 |
+
<img src="https://img.shields.io/badge/Docker-2496ED?style=for-the-badge&logo=docker&logoColor=white"/>
|
| 31 |
+
<img src="https://img.shields.io/badge/Gradio-F97316?style=for-the-badge&logo=gradio&logoColor=white"/>
|
| 32 |
+
<img src="https://img.shields.io/badge/OpenEnv-4B5563?style=for-the-badge&logo=envato&logoColor=white"/>
|
| 33 |
+
<img src="https://img.shields.io/badge/Amazon%20Bedrock-FF9900?style=for-the-badge&logo=amazonaws&logoColor=white"/>
|
| 34 |
+
</p>
|
| 35 |
+
<br>
|
| 36 |
+
|
| 37 |
+
<h1 align="center">
|
| 38 |
+
</h1>
|
| 39 |
+
<p align="center">
|
| 40 |
+
An OpenEnv-compatible reinforcement learning environment where an LLM agent must identify all 10 members of a coordinated fake account network hidden inside a synthetic social network. The agent learns via Reflexion and a dynamic hybrid rule/LLM policy , not via gradient updates or fine-tuning.
|
| 41 |
+
<br />
|
| 42 |
+
</p>
|
| 43 |
+
</p>
|
| 44 |
+
|
| 45 |
+
<br>
|
| 46 |
+
|
| 47 |
+
## Round 2 — Platform-Adaptive Trust & Safety
|
| 48 |
+
|
| 49 |
+
Round 2 makes detection **platform-aware end-to-end**. Each episode runs on a named platform (Instagram, Snapchat, X, LinkedIn, Reddit, …); a `PlatformPolicy` is compiled offline from real transparency-report text and cached per platform; the high-signal account fields start hidden and are revealed only by explicit tool actions; and a shared evaluation runner consults the LLM at exactly two decision points per suspicious account.
|
| 50 |
+
|
| 51 |
+
### How the policy lives end-to-end
|
| 52 |
+
|
| 53 |
+
```
|
| 54 |
+
transparency reports per-episode runtime
|
| 55 |
+
──────────────────── ───────────────────
|
| 56 |
+
Tavily search ──► Groq Llama-3.1 client.reset(task, seed)
|
| 57 |
+
extracts {π, fn_cost, fp_cost, │
|
| 58 |
+
harm_weight, primary_signal} env loads policy_cache/{platform}.json
|
| 59 |
+
│ │
|
| 60 |
+
▼ GET_POLICY (step 0, +0.20 bonus)
|
| 61 |
+
sanitize_pi() clamp π to [5e-4, 0.05] │
|
| 62 |
+
compute_threshold: DP1 (LLM): pick tool
|
| 63 |
+
θ_raw = C_fn·π / [C_fn·π + C_fp·(1−π)] reverse_image_search / analyze_bio
|
| 64 |
+
θ* = clamp(θ_raw / harm_weight, .01, .95) / check_ip / done
|
| 65 |
+
fp_penalty_weight = C_fp │
|
| 66 |
+
│ DP2 (LLM): flag / skip
|
| 67 |
+
▼ │
|
| 68 |
+
sanity_check_policy() warns on outliers SUBMIT
|
| 69 |
+
│ reward = tp − fp·C_fp − fn·0.3 + bonuses
|
| 70 |
+
policy_cache/{platform}.json (30-day TTL) decision_package + grader_score returned
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
The threshold `θ*` is read by the LLM in DP1/DP2 prompts; the FP-penalty `C_fp` is paid in the terminal reward at SUBMIT. Both come from the same compile-time computation — they cannot drift apart.
|
| 74 |
+
|
| 75 |
+
### What's new in this Round 2 cut
|
| 76 |
+
|
| 77 |
+
- **9 actions** including `get_policy`, `reverse_image_search`, `analyze_bio`, `check_ip`. `openenv.yaml` action_schema mirrors all nine.
|
| 78 |
+
- **Per-step reward delta** is returned on every `/step` (not only at SUBMIT), so per-action shaping like the GET_POLICY bonus and tool penalties are immediately visible.
|
| 79 |
+
- **`visible_accounts` is populated for every visible id at reset**, with hidden signals at `0.0 / ""` until tools reveal them.
|
| 80 |
+
- **`StepResponse` carries top-level `decision_package` and `grader_score`** after SUBMIT — callers no longer need to grep the message.
|
| 81 |
+
- **Blind FLAG is denied at flag time with `−0.15`** when the agent has neither inspected the account nor used any tool on it.
|
| 82 |
+
- **Generic platform support**: the policy compiler uses the platform-agnostic Tavily query `"{platform} fake account content policy enforcement 2024 2025"` and falls back to a generic policy when no hardcoded entry exists.
|
| 83 |
+
- **Two-decision-point eval runner** (`eval-models/_round2_runner.py`) drives episodes deterministically and exposes the LLM only at DP1 (tool pick) and DP2 (flag/skip). Six thin model shims (qwen, gemma, deepseek, llama, mistral, nvidia) plug in HF-router or Bedrock backends through a shared `_llm_adapters.py`.
|
| 84 |
+
- **`bash check.sh`** runs a 12-step system check covering health, all 9 actions, reward shaping, and the decision package.
|
| 85 |
+
|
| 86 |
+
Full architecture, formula audit, end-to-end policy lifecycle, sanity rules, scoring math, tool contracts, eval-runner internals, and quickstart commands live in **[`reference.md`](reference.md)** (single source of truth).
|
| 87 |
+
|
| 88 |
+
## Theme
|
| 89 |
+
|
| 90 |
+
**SUPPORT**
|
| 91 |
+
|
| 92 |
+
### Customer Service Agents
|
| 93 |
+
|
| 94 |
+
Complex environment where agents resolve multi-step queries using external tools and APIs.
|
| 95 |
+
|
| 96 |
+
## Problem Statement
|
| 97 |
+
|
| 98 |
+
**The task:** A social network contains fake accounts organised into a single coordinated ring of 10. The ring behaves in a coordinated way — same posting hour, same IP subnet, stolen celebrity photos, copy-paste bios. The agent must find all 10 by navigating a limited step budget, inspecting accounts, and flagging suspects.
|
| 99 |
+
|
| 100 |
+
## Proposed Solution
|
| 101 |
+
|
| 102 |
+
An OpenEnv-compatible reinforcement learning environment where an LLM agent must identify all 10 members of a coordinated fake account ring hidden inside a synthetic social network. The agent learns via **Reflexion** and a **dynamic hybrid rule/LLM policy** — not via gradient updates or fine-tuning.
|
| 103 |
+
|
| 104 |
+
---
|
| 105 |
+
## Novelty Highlights
|
| 106 |
+
|
| 107 |
+
- **Adaptive Hybrid Intelligence (Rules + LLM):** Unlike static ensembles, GraphStrike dynamically blends deterministic rules and LLM reasoning using a trust gate, shifting control as performance improves.
|
| 108 |
+
- **Learning Without Fine-Tuning:** Instead of updating model weights, the agent learns through Reflexion lessons and best-trajectory memory injected into future prompts.
|
| 109 |
+
- **Graph-First Detection Pipeline:** Detection is not account-by-account only; it uses cascade effects, neighbor propagation, and multi-hop graph expansion to uncover coordinated rings.
|
| 110 |
+
- **Math-Grounded Decision Control:** Risk composition, trust calibration, and grader alignment are formula-driven, making behavior interpretable and reproducible.
|
| 111 |
+
- **Adversarial Evasion Benchmarking:** Hard-mode includes timed evasion events, so success reflects robustness under disruption rather than overfitting to static patterns.
|
| 112 |
+
- **Safety-Net by Design:** High-confidence rule overrides prevent catastrophic LLM errors while preserving LLM flexibility for strategic exploration.
|
| 113 |
+
---
|
| 114 |
+
|
| 115 |
+
## Performance Summary
|
| 116 |
+
|
| 117 |
+
We evaluate GraphStrike's hybrid rule/LLM policy across multiple *frontier models to measure how well each model handles the investigation task. All runs use
|
| 118 |
+
the same inference pipeline (`inference.py`) with identical system prompts and structured logging. Each model ran: (1) seed=0 on all 3 tasks, and
|
| 119 |
+
(2) seeds 0-2 on all 3 tasks for variance measurement.*
|
| 120 |
+
|
| 121 |
+
**Seed=0 scores (single episode per task):**
|
| 122 |
+
|
| 123 |
+
<p align="center">
|
| 124 |
+
<img src="images/table1.png" alt="Model Performance Table" width="1600"/>
|
| 125 |
+
</p>
|
| 126 |
+
<br>
|
| 127 |
+
|
| 128 |
+
**3-seed variance scores (mean across seeds 0, 1, 2):**
|
| 129 |
+
|
| 130 |
+
<p align="center">
|
| 131 |
+
<img src="images/table2.png" alt="Model Performance Table" width="1600"/>
|
| 132 |
+
</p>
|
| 133 |
+
<br>
|
| 134 |
+
|
| 135 |
+
**Rule-Based Baseline (no LLM, deterministic)**
|
| 136 |
+
|
| 137 |
+
<p align="center">
|
| 138 |
+
<img src="images/table3.png" alt="Model Performance Table" width="1600"/>
|
| 139 |
+
</p>
|
| 140 |
+
<br>
|
| 141 |
+
|
| 142 |
+
---
|
| 143 |
+
## Table of Contents
|
| 144 |
+
|
| 145 |
+
1. [What This Is](#1-what-this-is)
|
| 146 |
+
2. [The Problem: How Fake Detection Actually Works](#2-the-problem-how-fake-detection-actually-works)
|
| 147 |
+
3. [Synthetic Data Generation](#3-synthetic-data-generation)
|
| 148 |
+
4. [Data Model](#4-data-model)
|
| 149 |
+
5. [The RL Environment](#5-the-rl-environment)
|
| 150 |
+
6. [Risk Scoring Mathematics](#6-risk-scoring-mathematics)
|
| 151 |
+
8. [The LLM Policy (Qwen3 via Bedrock)](#8-the-llm-policy-qwen3-via-bedrock)
|
| 152 |
+
9. [Reflexion — How the Agent Learns](#9-reflexion--how-the-agent-learns)
|
| 153 |
+
10. [Hybrid Policy — The Novel Contribution](#10-hybrid-policy--the-novel-contribution)
|
| 154 |
+
11. [Training Loop End-to-End](#11-training-loop-end-to-end)
|
| 155 |
+
12. [API Reference](#12-api-reference)
|
| 156 |
+
13. [Docker Deployment](#13-docker-deployment)
|
| 157 |
+
14. [Submission Requirements](#14-submission-requirements)
|
| 158 |
+
15. [Verification & Validation](#15-verification--validation)
|
| 159 |
+
|
| 160 |
+
---
|
| 161 |
+
|
| 162 |
+
## 1. What is this !?
|
| 163 |
+
|
| 164 |
+
This is an **OpenEnv hackathon** submission. OpenEnv is a framework for building RL environments with a standard microservice interface (`/reset`, `/step`, `/state`) so that any agent implementation can plug in.
|
| 165 |
+
|
| 166 |
+
**What makes this non-trivial:**
|
| 167 |
+
|
| 168 |
+
- The network is large (50–1000 accounts depending on difficulty).
|
| 169 |
+
- Fake accounts are mixed with innocent high-signal "decoy" accounts.
|
| 170 |
+
- In hard mode, the gang actively evades — dropping intra-gang follows, renaming profiles — while the agent is mid-investigation.
|
| 171 |
+
- The agent cannot see the full network upfront: it must explore via INSPECT and INVESTIGATE_NETWORK actions, spending steps to reveal information.
|
| 172 |
+
|
| 173 |
+
**What makes the learning novel:**
|
| 174 |
+
|
| 175 |
+
- The LLM (inference via AWS Bedrock) cannot be fine-tuned — it is a black-box API.
|
| 176 |
+
- The agent learns via **Reflexion**: post-episode lessons are written back into memory and injected into every future prompt.
|
| 177 |
+
- A **dynamic hybrid policy** (α-weighted) blends the LLM with a deterministic rule engine, with the blend weight α updating based on recent win rate. Rules dominate early; the LLM takes over as it proves itself.
|
| 178 |
+
|
| 179 |
+
### System Architecture
|
| 180 |
+
|
| 181 |
+

|
| 182 |
+
|
| 183 |
+
---
|
| 184 |
+
|
| 185 |
+
## 2. The Problem: How Fake Detection Actually Works
|
| 186 |
+
|
| 187 |
+
A real-world fake account detector does **not** read post content. Detection relies on three categories of signals computed from metadata:
|
| 188 |
+
|
| 189 |
+
### Signal Hierarchy (Node -> Behavioral -> Graph)
|
| 190 |
+
|
| 191 |
+

|
| 192 |
+
|
| 193 |
+
- **Node signals (offline):** content fingerprints like photo reuse, bio-template similarity, and comment repetition provide the first suspicion layer.
|
| 194 |
+
- **Behavioral signals (temporal/device):** coordinated posting hour, account-age clustering, and shared IP subnet add stronger gang-level evidence.
|
| 195 |
+
- **Graph signals (live at INSPECT):** mutual follows, flagged-neighbor growth, and cluster alignment are hardest to evade, so they carry the highest weight in risk scoring.
|
| 196 |
+
- **False-positive control:** high-legitimacy hubs (for example celebrities) are down-weighted through hub-legitimacy discounting.
|
| 197 |
+
|
| 198 |
+
---
|
| 199 |
+
|
| 200 |
+
## 3. Synthetic Data Generation
|
| 201 |
+
|
| 202 |
+
**File:** `server/generator.py`
|
| 203 |
+
|
| 204 |
+
Episodes are generated deterministically by seed. 150 episodes are pre-generated (50 per task) and cached as JSON files in `episodes/`.
|
| 205 |
+
|
| 206 |
+
### Network Composition
|
| 207 |
+
|
| 208 |
+
| Task | Network size | Gang | Decoys | Real | Max steps |
|
| 209 |
+
|---|---|---|---|---|---|
|
| 210 |
+
| easy | 50 | 10 | 0 | 40 | 30 |
|
| 211 |
+
| medium | 200 | 10 | 20 | 170 | 50 |
|
| 212 |
+
| hard | 1000 | 10 | 50 | 940 | 80 |
|
| 213 |
+
|
| 214 |
+
- **Gang accounts:** All 10 share `base_age` (same creation week), tightly clustered `avg_post_hour`, high `photo_reuse_score`/`bio_template_score`, `comment_repeat_score` in [0.60, 0.90], `ip_cluster_id = "ip_gang_{seed}"`, and dense intra-gang follow edges (density 0.60–0.80).
|
| 215 |
+
- **Real accounts:** Log-normal follower distributions, unique IP clusters, low fake scores.
|
| 216 |
+
- **Decoy accounts** (medium/hard): Real accounts with elevated fraud scores (0.20–0.40 range) — they look suspicious but are NOT gang members and penalise reckless flagging.
|
| 217 |
+
- **Celebrity accounts** (2 per episode): 100k–5M followers, very low fake scores, high `hub_legitimacy_score`.
|
| 218 |
+
- **Zero-edge isolates** (2 per episode): No edges — test whether the agent wastes steps on disconnected nodes.
|
| 219 |
+
|
| 220 |
+
---
|
| 221 |
+
|
| 222 |
+
## 4. Data Model
|
| 223 |
+
|
| 224 |
+
**File:** `models.py`
|
| 225 |
+
|
| 226 |
+
### ActionType
|
| 227 |
+
|
| 228 |
+
| Value | Cost | Effect |
|
| 229 |
+
|---|---|---|
|
| 230 |
+
| `inspect` | 1 step | Reveals full `AccountProfile` + follow list |
|
| 231 |
+
| `investigate_network` | 2 steps | Expands 2 hops; reveals account IDs only |
|
| 232 |
+
| `flag` | 0 steps | Marks account as gang member; triggers SUSPECT cascade |
|
| 233 |
+
| `unflag` | 0 steps | Removes flag; clears CONFIRMED_FAKE status |
|
| 234 |
+
| `submit` | 0 steps | Ends episode; triggers scoring |
|
| 235 |
+
|
| 236 |
+
### AccountProfile — key fields
|
| 237 |
+
|
| 238 |
+
| Category | Fields |
|
| 239 |
+
|---|---|
|
| 240 |
+
| Raw counts | `follower_count`, `following_count`, `post_count` |
|
| 241 |
+
| Temporal | `avg_post_hour`, `account_age_days` |
|
| 242 |
+
| Content pipeline (0–1) | `photo_reuse_score`, `bio_template_score`, `comment_repeat_score` |
|
| 243 |
+
| IP/device | `shared_ip_count`, `ip_cluster_id` |
|
| 244 |
+
| Graph (live at INSPECT) | `mutual_follow_rate`, `flagged_neighbor_count`, `avg_neighbor_photo_reuse`, `post_hour_cluster_score` |
|
| 245 |
+
| Risk breakdown | `fake_risk_score`, `node_risk`, `behavior_risk`, `graph_risk`, `hub_legitimacy_score` |
|
| 246 |
+
| Evasion/status | `name_change_count`, `status` (NORMAL/SUSPECT/CONFIRMED_FAKE) |
|
| 247 |
+
|
| 248 |
+
### FakeGangObservation — what the agent sees each step
|
| 249 |
+
|
| 250 |
+
`done`, `reward`, `visible_accounts`, `visible_account_ids`, `flagged_ids`, `inspected_ids`, `suspect_ids`, `graph_edges`, `steps_remaining`, `evasion_triggered`, `evasion_count`, `task`, `message`
|
| 251 |
+
|
| 252 |
+
---
|
| 253 |
+
|
| 254 |
+
## 5. The RL Environment
|
| 255 |
+
|
| 256 |
+
**File:** `server/environment.py`
|
| 257 |
+
|
| 258 |
+
### Episode Lifecycle & Action Mechanics
|
| 259 |
+
|
| 260 |
+

|
| 261 |
+
|
| 262 |
+
**FLAG cascade (dual):** When FLAG(X) is called — (1) every visible account that X follows becomes SUSPECT via the follow-graph, and (2) every visible account sharing X's `ip_cluster_id` becomes SUSPECT. Gang members share `ip_gang_{seed}`; real accounts have unique IPs → zero false positives.
|
| 263 |
+
|
| 264 |
+
### Reward Function
|
| 265 |
+
|
| 266 |
+
```
|
| 267 |
+
base_reward = tp×1.0 − fp×0.5 − fn×0.3
|
| 268 |
+
|
| 269 |
+
Win condition:
|
| 270 |
+
easy/medium: recall ≥ 0.8 AND precision ≥ 0.7
|
| 271 |
+
hard: recall ≥ 0.9 AND precision ≥ 0.8
|
| 272 |
+
|
| 273 |
+
Bonuses:
|
| 274 |
+
+5.0 full win
|
| 275 |
+
+3.0 perfect recall
|
| 276 |
+
+2.0 partial win (high recall, low precision)
|
| 277 |
+
+1.0 efficiency (SUBMIT with ≥50% steps remaining)
|
| 278 |
+
−1.0 per evasion event (hard mode)
|
| 279 |
+
−2.0 forced submit (ran out of steps)
|
| 280 |
+
```
|
| 281 |
+
|
| 282 |
+
### Evasion (hard mode)
|
| 283 |
+
|
| 284 |
+
- **`unfollow_intragang`:** 30% of intra-gang edges randomly removed mid-investigation — destroys graph signal. Fires 4 times (steps 15, 30, 45, 60).
|
| 285 |
+
- **`rename_count`:** Random gang members get `name_change_count += 1` — a visual evasion signal.
|
| 286 |
+
|
| 287 |
+
---
|
| 288 |
+
|
| 289 |
+
## 6. Risk Scoring Mathematics
|
| 290 |
+
|
| 291 |
+
**File:** `server/scoring.py` — all functions are stateless and deterministic.
|
| 292 |
+
|
| 293 |
+

|
| 294 |
+
|
| 295 |
+

|
| 296 |
+
|
| 297 |
+

|
| 298 |
+
|
| 299 |
+
---
|
| 300 |
+
|
| 301 |
+
## 8. The LLM Policy (Qwen3 via Bedrock)
|
| 302 |
+
|
| 303 |
+
**File:** `agent/policy.py`
|
| 304 |
+
|
| 305 |
+
**Model:** `qwen.qwen3-next-80b-a3b` via AWS Bedrock Converse API (`maxTokens=512, temperature=0.4`)
|
| 306 |
+
|
| 307 |
+
### Prompt Structure
|
| 308 |
+
|
| 309 |
+
Every step, the policy builds a prompt from three components:
|
| 310 |
+
|
| 311 |
+
```
|
| 312 |
+
[reflections from past episodes] ← grows richer every episode
|
| 313 |
+
[best trajectory few-shot example] ← best win ever, showing the full action log
|
| 314 |
+
━━━ CURRENT CASE ━━━
|
| 315 |
+
[formatted observation] ← status badges, risk scores, suspect list
|
| 316 |
+
What is your next action?
|
| 317 |
+
```
|
| 318 |
+
|
| 319 |
+
Accounts in the observation are **sorted by `fake_risk_score` descending**, with status badges prepended. `fnbr=N(!)` highlights when `flagged_neighbor_count > 0`; `[HUB?]` warns the LLM not to flag high-legitimacy accounts.
|
| 320 |
+
|
| 321 |
+
### Required Response Format
|
| 322 |
+
|
| 323 |
+
```xml
|
| 324 |
+
<thinking>
|
| 325 |
+
Reasoning — which account is most suspicious and why.
|
| 326 |
+
</thinking>
|
| 327 |
+
<action>
|
| 328 |
+
INSPECT acc_0041
|
| 329 |
+
</action>
|
| 330 |
+
```
|
| 331 |
+
|
| 332 |
+
If parsing fails, a heuristic fallback inspects the highest-scored uninspected account. Retries use exponential backoff (1s, 2s, 4s) up to 3 attempts.
|
| 333 |
+
|
| 334 |
+
---
|
| 335 |
+
|
| 336 |
+
## 9. Reflexion — How the Agent Learns
|
| 337 |
+
|
| 338 |
+
**Files:** `agent/reflection.py`, `agent/memory.py`
|
| 339 |
+
|
| 340 |
+
The agent **cannot** update Qwen3's weights — Bedrock is a black-box API. Instead, it learns via **Reflexion**: post-episode lessons are written as text and injected into future prompts.
|
| 341 |
+
|
| 342 |
+
### Reflexion Learning Loop
|
| 343 |
+
|
| 344 |
+

|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
```
|
| 349 |
+
Episode N:
|
| 350 |
+
1. LLM acts using: system_prompt + reflections[last 4] + best_trajectory
|
| 351 |
+
2. Episode ends → WIN or LOSS
|
| 352 |
+
3. Post-episode:
|
| 353 |
+
LOSS → generate_reflection(action_log, outcome) → lesson stored
|
| 354 |
+
WIN → save trajectory if better reward + generate_success_reflection
|
| 355 |
+
|
| 356 |
+
Episode N+1:
|
| 357 |
+
→ last 4 reflections + best win trajectory injected into prompt
|
| 358 |
+
→ LLM has learned from its past
|
| 359 |
+
```
|
| 360 |
+
|
| 361 |
+
**Example generated reflection:**
|
| 362 |
+
> *"The starting accounts were all real; I wasted 8 steps inspecting low-signal nodes before pivoting. When photo_reuse and bio_template are both below 0.3 after 3 inspections, immediately use INVESTIGATE_NETWORK to jump to a different graph region."*
|
| 363 |
+
|
| 364 |
+
All memory persists in a Docker volume (`memory/`) across container restarts — reflections, best trajectories, win history, and α values per task.
|
| 365 |
+
|
| 366 |
+
---
|
| 367 |
+
|
| 368 |
+
## 10. Hybrid Policy — The Novel Contribution
|
| 369 |
+
|
| 370 |
+
**File:** `agent/hybrid_policy.py`
|
| 371 |
+
|
| 372 |
+
**Key insight:** A new LLM agent starts dumb but improves over time. A rule engine is always consistent but cannot adapt. The hybrid policy exploits both — rules provide a safety net early while the LLM builds its track record; once the LLM proves itself, rules step back.
|
| 373 |
+
|
| 374 |
+
### Architecture
|
| 375 |
+
|
| 376 |
+

|
| 377 |
+
|
| 378 |
+
### Alpha (α): The Trust Weight
|
| 379 |
+
|
| 380 |
+
α is a per-task value in [0.20, cap] representing current trust in the LLM:
|
| 381 |
+
|
| 382 |
+
```
|
| 383 |
+
reflection_factor = min(1.0, n_reflections / 4.0)
|
| 384 |
+
raw = 0.20 + reflection_factor × (0.80 × recent_win_rate + 0.12)
|
| 385 |
+
α = clamp(raw, 0.20, cap)
|
| 386 |
+
```
|
| 387 |
+
|
| 388 |
+
| Task | α cap | Rationale |
|
| 389 |
+
|---|---|---|
|
| 390 |
+
| easy | 0.50 | Rule engine alone achieves ~91% — LLM should assist, not override |
|
| 391 |
+
| medium | 0.70 | Decoys require some LLM judgment, but cascade must stay |
|
| 392 |
+
| hard | 0.85 | LLM needs latitude for evasion adaptation, but safety rules remain |
|
| 393 |
+
|
| 394 |
+
**Alpha trajectory over training (easy task, cap=0.50):**
|
| 395 |
+
|
| 396 |
+
| Episode | Win rate | Reflections | α (capped) |
|
| 397 |
+
|---|---|---|---|
|
| 398 |
+
| 1 | 0% | 0 | 0.20 |
|
| 399 |
+
| 5 | 20% | 4 | 0.48 |
|
| 400 |
+
| 10 | 50% | 9 | **0.50** |
|
| 401 |
+
| 20 | 80% | 19 | **0.50** |
|
| 402 |
+
|
| 403 |
+
<br>
|
| 404 |
+
|
| 405 |
+

|
| 406 |
+
|
| 407 |
+
### Rule Confidence Levels
|
| 408 |
+
|
| 409 |
+
| Situation | Action | Confidence |
|
| 410 |
+
|---|---|---|
|
| 411 |
+
| Steps remaining = 0 | SUBMIT | 1.00 |
|
| 412 |
+
| Uninspected SUSPECT accounts exist | INSPECT suspects[0] | 0.95 |
|
| 413 |
+
| `fake_risk ≥ 0.85` | FLAG that account | 0.95 |
|
| 414 |
+
| `fake_risk` in [threshold, 0.85) | FLAG that account | 0.70+ |
|
| 415 |
+
| 10 accounts already flagged | SUBMIT | 0.85 |
|
| 416 |
+
| Steps remaining ≤ 3 | SUBMIT | 0.90 |
|
| 417 |
+
| Uninspected accounts available | INSPECT top candidate | 0.30 |
|
| 418 |
+
|
| 419 |
+
At **α=0.20** (early): rules dominate (~90% of decisions). At **α=0.50** (moderate): LLM controls exploration; rules control safety. At **α=0.85** (high): LLM controls most decisions; rules only override forced submits and uninspected suspects.
|
| 420 |
+
|
| 421 |
+
α is saved to `memory/alpha_{task}.json` and persists across Docker restarts — the agent doesn't reset to 0.20 every time.
|
| 422 |
+
|
| 423 |
+
---
|
| 424 |
+
|
| 425 |
+
## 11. Training Loop End-to-End
|
| 426 |
+
|
| 427 |
+
**File:** `train.py`
|
| 428 |
+
|
| 429 |
+
### Curriculum
|
| 430 |
+
|
| 431 |
+
| Phase | Episodes | Task | Goal |
|
| 432 |
+
|---|---|---|---|
|
| 433 |
+
| 1 | 1–20 | easy | Learn basic signal thresholds, build first reflections |
|
| 434 |
+
| 2 | 21–35 | medium | Handle decoys, learn evasion response |
|
| 435 |
+
| 3 | 36–50 | hard | Feature-only detection, persistent evasion |
|
| 436 |
+
|
| 437 |
+
Seeds rotate deterministically: `seed = (episode_num + task_offset) % 50`
|
| 438 |
+
|
| 439 |
+
### Per-Episode Flow
|
| 440 |
+
|
| 441 |
+
```
|
| 442 |
+
for ep in range(n_episodes):
|
| 443 |
+
|
| 444 |
+
1. DETERMINE TASK curriculum_task(ep) or fixed task
|
| 445 |
+
2. COMPUTE ALPHA compute_alpha(win_rate, n_reflections, task)
|
| 446 |
+
3. LOAD CONTEXT last 4 reflections + best win trajectory
|
| 447 |
+
4. RUN EPISODE while not obs.done:
|
| 448 |
+
blend(rule_action, llm_action, rule_conf, α)
|
| 449 |
+
→ obs = env.step(final)
|
| 450 |
+
5. POST-EPISODE record_win → update α → generate reflection
|
| 451 |
+
6. LOG task | win/loss | reward | recall | precision | α | modes
|
| 452 |
+
```
|
| 453 |
+
|
| 454 |
+
Episode metrics (flushed to `runs/metrics.jsonl` every 5 episodes) include: `episode`, `task`, `won`, `reward`, `recall`, `precision`, `steps_used`, `alpha_used`, `mode_agree`, `mode_rule`, `mode_llm`, `n_reflections_used`.
|
| 455 |
+
|
| 456 |
+
You can watch the transition: early episodes have high `rule` counts; later episodes have high `agree` counts (LLM learned to make the same decisions as the rules, but also brings strategic reasoning the rules can't).
|
| 457 |
+
|
| 458 |
+
---
|
| 459 |
+
|
| 460 |
+
## 12. API Reference
|
| 461 |
+
|
| 462 |
+
**File:** `server/app.py`
|
| 463 |
+
|
| 464 |
+
| Endpoint | Method | Description |
|
| 465 |
+
|---|---|---|
|
| 466 |
+
| `/health` | GET | `{"status": "healthy"}` |
|
| 467 |
+
| `/tasks` | GET | Task list + `action_schema` + `score_range: [0.0, 1.0]` |
|
| 468 |
+
| `/reset` | POST | Accepts `{task, seed}` → returns initial observation |
|
| 469 |
+
| `/step` | POST | Accepts any `FakeGangAction` → returns updated observation |
|
| 470 |
+
| `/state` | GET | Current episode metadata (step count, task, score) |
|
| 471 |
+
| `/grader` | GET | Normalised [0.0, 1.0] score after SUBMIT |
|
| 472 |
+
| `/baseline` | POST | Runs rule-based agent on all 3 tasks, returns scores |
|
| 473 |
+
|
| 474 |
+
**Baseline performance:**
|
| 475 |
+
|
| 476 |
+
| Task | Seed=0 score | Win rate (50 seeds) | Mean score (50 seeds) |
|
| 477 |
+
|---|---|---|---|
|
| 478 |
+
| easy | 0.91 | 100% | ~0.91 |
|
| 479 |
+
| medium | 0.906 | 84% | ~0.77 |
|
| 480 |
+
| hard | 0.9038 | 52% | ~0.47 |
|
| 481 |
+
|
| 482 |
+
---
|
| 483 |
+
|
| 484 |
+
## 13. Docker Deployment
|
| 485 |
+
|
| 486 |
+
```bash
|
| 487 |
+
# Build
|
| 488 |
+
docker build -f server/Dockerfile -t graphstrike .
|
| 489 |
+
|
| 490 |
+
# Run
|
| 491 |
+
docker run -it \
|
| 492 |
+
-e AWS_ACCESS_KEY_ID=your_key \
|
| 493 |
+
-e AWS_SECRET_ACCESS_KEY=your_secret \
|
| 494 |
+
-v $(pwd)/memory:/app/memory \
|
| 495 |
+
-v $(pwd)/runs:/app/runs \
|
| 496 |
+
-p 8000:8000 \
|
| 497 |
+
graphstrike
|
| 498 |
+
```
|
| 499 |
+
|
| 500 |
+
The `memory/` and `runs/` volumes preserve all learning between container restarts.
|
| 501 |
+
|
| 502 |
+
### Environment Variables
|
| 503 |
+
|
| 504 |
+
| Variable | Default | Description |
|
| 505 |
+
|---|---|---|
|
| 506 |
+
| `AWS_ACCESS_KEY_ID` | (required) | For Bedrock/Qwen3 access |
|
| 507 |
+
| `AWS_SECRET_ACCESS_KEY` | (required) | For Bedrock/Qwen3 access |
|
| 508 |
+
| `AWS_DEFAULT_REGION` | `us-east-1` | Bedrock region |
|
| 509 |
+
| `TRAIN_TASK` | (curriculum) | Fix to `easy`/`medium`/`hard` |
|
| 510 |
+
| `TRAIN_EPISODES` | `50` | Total training episodes |
|
| 511 |
+
| `TRAIN_TEMP` | `0.4` | LLM sampling temperature |
|
| 512 |
+
| `TRAIN_VERBOSE` | `0` | Set `1` for per-step action logging |
|
| 513 |
+
| `SERVER_PORT` | `8000` | FastAPI port |
|
| 514 |
+
|
| 515 |
+
### Startup Sequence (`run.sh`)
|
| 516 |
+
|
| 517 |
+
```
|
| 518 |
+
1. Validate AWS credentials
|
| 519 |
+
2. python server/generator.py → generates 150 episode JSON files
|
| 520 |
+
3. uvicorn server.app:app → starts the environment server
|
| 521 |
+
4. Health check polling → waits until /health responds
|
| 522 |
+
5. python train.py → runs the full training loop
|
| 523 |
+
```
|
| 524 |
+
|
| 525 |
+
---
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
### Full HTTP validation
|
| 529 |
+
|
| 530 |
+
```bash
|
| 531 |
+
python3 -m uvicorn server.app:app --port 8001 &
|
| 532 |
+
sleep 3
|
| 533 |
+
python3 validate.py --url http://localhost:8001
|
| 534 |
+
# Expected: Results: 24/24 passed — all OK
|
| 535 |
+
```
|
| 536 |
+
|
| 537 |
+
### Deployed Endpoint Verification
|
| 538 |
+
|
| 539 |
+
```bash
|
| 540 |
+
curl https://pandago-graphstrike.hf.space/health
|
| 541 |
+
# → {"status": "healthy"}
|
| 542 |
+
|
| 543 |
+
curl https://pandago-graphstrike.hf.space/tasks
|
| 544 |
+
# → {"tasks": ["easy","medium","hard"], "action_schema": {...}, "score_range": [0.0, 1.0]}
|
| 545 |
+
|
| 546 |
+
curl -X POST https://pandago-graphstrike.hf.space/baseline
|
| 547 |
+
# → {"scores": {"easy": 0.91, "medium": 0.906, "hard": 0.9038}, "agent": "rule_based"}
|
| 548 |
+
```
|
| 549 |
+
|
| 550 |
+
---
|
| 551 |
+
|
| 552 |
+

|
| 553 |
+
|
| 554 |
+
## Developed with ❤️ by Team ComputeXOR
|
| 555 |
+
|
| 556 |
+
### {
|
| 557 |
+
|
| 558 |
+
### [Sai Nivedh](https://github.com/SaiNivedh26) ,
|
| 559 |
+
|
| 560 |
+
### [Charuvarthan](https://github.com/Charuvarthan-T) ,
|
| 561 |
+
|
| 562 |
+
### [Sajeev](https://github.com/SajeevSenthil)
|
| 563 |
+
|
| 564 |
+
### }
|
__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# GraphStrike — OpenEnv environment for coordinated fake account ring detection
|
agent/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Agent package — LLM policy, memory, and reflection for the Fake Gang Detection env
|
agent/hybrid_policy.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Hybrid confidence-weighted policy for the Fake Gang Detection agent.
|
| 2 |
+
|
| 3 |
+
Blends a deterministic rule-based agent with the LLM (Qwen3) agent
|
| 4 |
+
using a dynamic trust weight α ∈ [0.20, 1.00]:
|
| 5 |
+
|
| 6 |
+
α = 0.20 + reflection_factor × (0.80 × recent_win_rate + 0.12)
|
| 7 |
+
|
| 8 |
+
Action selection when LLM and rules DISAGREE:
|
| 9 |
+
• rules win if rule_confidence >= α (α low → rules trusted more)
|
| 10 |
+
• LLM wins if rule_confidence < α (α high → LLM trusted more)
|
| 11 |
+
When they AGREE the action is used as-is (mode="agree").
|
| 12 |
+
|
| 13 |
+
α dynamics:
|
| 14 |
+
α starts at 0.20 (rules dominate — LLM has no history yet).
|
| 15 |
+
As the LLM accumulates wins and reflections, α climbs toward 1.0.
|
| 16 |
+
Episode 1, 0 wins → α ≈ 0.20 (almost always rule-guided)
|
| 17 |
+
Episode 20, 70% wins, 4 reflections → α ≈ 0.76 (LLM leads, rules as safety net)
|
| 18 |
+
Episode 40, 90% wins, 8 reflections → α ≈ 0.92 (LLM trusted, rules intervene rarely)
|
| 19 |
+
|
| 20 |
+
Rule confidence levels:
|
| 21 |
+
1.00 — forced SUBMIT (out of steps)
|
| 22 |
+
0.95 — INSPECT a SUSPECT account (cascade-elevated neighbor)
|
| 23 |
+
0.90 — FLAG an account with fake_risk_score ≥ 0.85
|
| 24 |
+
0.80 — SUBMIT with 10 flags in place
|
| 25 |
+
0.70 — FLAG an account with fake_risk_score in [threshold, 0.85)
|
| 26 |
+
0.30 — exploratory INSPECT (no strong signal, just scanning)
|
| 27 |
+
|
| 28 |
+
This means:
|
| 29 |
+
• At α=0.20 → rules win all disagreements (confidence always ≥ 0.20)
|
| 30 |
+
• At α=0.50 → rules win when confidence ≥ 0.50 (suspect/high-risk actions)
|
| 31 |
+
• At α=0.80 → rules win only when confidence ≥ 0.80 (critical overrides only)
|
| 32 |
+
• At α=1.00 → rules never win (pure LLM mode)
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
from __future__ import annotations
|
| 36 |
+
|
| 37 |
+
import sys
|
| 38 |
+
from pathlib import Path
|
| 39 |
+
from typing import Dict, List, Optional, Tuple
|
| 40 |
+
|
| 41 |
+
_ROOT = Path(__file__).parent.parent
|
| 42 |
+
sys.path.insert(0, str(_ROOT))
|
| 43 |
+
sys.path.insert(0, str(_ROOT / "server"))
|
| 44 |
+
|
| 45 |
+
from models import ActionType, FakeGangAction, FakeGangObservation
|
| 46 |
+
from agent.policy import get_action
|
| 47 |
+
|
| 48 |
+
# Per-task thresholds mirror inference.py
|
| 49 |
+
_THRESHOLDS: Dict[str, float] = {
|
| 50 |
+
"easy": 0.60,
|
| 51 |
+
"medium": 0.50,
|
| 52 |
+
"hard": 0.45,
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
# Bootstrap raw-feature score — same calibration as inference.py
|
| 56 |
+
# 0.30*photo + 0.20*bio + 0.50*comment_repeat >= 0.40
|
| 57 |
+
# Gang member (any task): ~0.57–0.78; decoy: ~0.25; real: ~0.07
|
| 58 |
+
_BOOTSTRAP_RAW_THRESHOLD = 0.40
|
| 59 |
+
_SHARED_IP_GANG_THRESHOLD = 5
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
# ---------------------------------------------------------------------------
|
| 63 |
+
# Rule-based single-step decision
|
| 64 |
+
# ---------------------------------------------------------------------------
|
| 65 |
+
|
| 66 |
+
def get_rule_action(obs: FakeGangObservation) -> Tuple[FakeGangAction, float]:
|
| 67 |
+
"""Return the rule-based action for the current observation and its confidence.
|
| 68 |
+
|
| 69 |
+
Returns:
|
| 70 |
+
(action, confidence) where confidence ∈ [0.0, 1.0]
|
| 71 |
+
"""
|
| 72 |
+
threshold = _THRESHOLDS.get(obs.task, 0.50)
|
| 73 |
+
|
| 74 |
+
# Priority 1 — forced end (out of steps)
|
| 75 |
+
if obs.steps_remaining <= 0:
|
| 76 |
+
return FakeGangAction(action_type=ActionType.SUBMIT), 1.00
|
| 77 |
+
|
| 78 |
+
# Priority 2 — INSPECT SUSPECT accounts (auto-cascaded from FLAG)
|
| 79 |
+
uninspected_suspects = [s for s in obs.suspect_ids if s not in obs.inspected_ids]
|
| 80 |
+
if uninspected_suspects:
|
| 81 |
+
return (
|
| 82 |
+
FakeGangAction(action_type=ActionType.INSPECT, account_id=uninspected_suspects[0]),
|
| 83 |
+
0.95,
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
# Priority 3 — FLAG high-risk inspected accounts
|
| 87 |
+
# Two signal paths: composite fake_risk (active post-cascade) OR bootstrap raw
|
| 88 |
+
# node score (catches first gang members before graph signals are established).
|
| 89 |
+
for p in sorted(obs.visible_accounts, key=lambda x: x.fake_risk_score, reverse=True):
|
| 90 |
+
if p.account_id in obs.flagged_ids:
|
| 91 |
+
continue
|
| 92 |
+
if p.hub_legitimacy_score > 0.75:
|
| 93 |
+
continue # protect celebrities
|
| 94 |
+
|
| 95 |
+
bootstrap_raw = (
|
| 96 |
+
0.30 * p.photo_reuse_score
|
| 97 |
+
+ 0.20 * p.bio_template_score
|
| 98 |
+
+ 0.50 * p.comment_repeat_score
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
if p.shared_ip_count >= _SHARED_IP_GANG_THRESHOLD:
|
| 102 |
+
return FakeGangAction(action_type=ActionType.FLAG, account_id=p.account_id), 0.97
|
| 103 |
+
|
| 104 |
+
if p.fake_risk_score >= threshold:
|
| 105 |
+
confidence = min(0.95, 0.70 + (p.fake_risk_score - threshold) * 0.60)
|
| 106 |
+
return FakeGangAction(action_type=ActionType.FLAG, account_id=p.account_id), confidence
|
| 107 |
+
|
| 108 |
+
if bootstrap_raw >= _BOOTSTRAP_RAW_THRESHOLD:
|
| 109 |
+
# Bootstrap confidence: how far above threshold the raw score is
|
| 110 |
+
confidence = min(0.88, 0.60 + (bootstrap_raw - _BOOTSTRAP_RAW_THRESHOLD) * 0.80)
|
| 111 |
+
return FakeGangAction(action_type=ActionType.FLAG, account_id=p.account_id), confidence
|
| 112 |
+
|
| 113 |
+
# Priority 4 — INVESTIGATE_NETWORK to chain through the gang cluster.
|
| 114 |
+
#
|
| 115 |
+
# The problem: FLAG only cascades SUSPECT to already-visible neighbors. Gang members
|
| 116 |
+
# follow each other but aren't visible until we expand the graph. INVESTIGATE_NETWORK
|
| 117 |
+
# (2-hop expansion) + the environment's re-cascade makes them SUSPECT immediately,
|
| 118 |
+
# so Priority 2 picks them up in the next step.
|
| 119 |
+
#
|
| 120 |
+
# How many investigations have we done this episode?
|
| 121 |
+
# Each INVESTIGATE_NETWORK on an already-inspected account: +2 steps, +0 inspected_ids.
|
| 122 |
+
# Each INSPECT: +1 step, +1 inspected_ids.
|
| 123 |
+
# So: n_investigate ≈ (steps_used - len(inspected_ids)) // 2
|
| 124 |
+
# This self-throttles: fires once per newly flagged account (one investigation per flag).
|
| 125 |
+
#
|
| 126 |
+
# Max investigations per episode is task-capped to protect the step budget:
|
| 127 |
+
# easy=1 (30 step budget, intra-gang density 0.80 → one expand covers all)
|
| 128 |
+
# medium=2 (50 steps, density 0.70 + evasion)
|
| 129 |
+
# hard=3 (80 steps, density 0.60 + heavy evasion)
|
| 130 |
+
_max_investigate = {"easy": 1, "medium": 2, "hard": 3}
|
| 131 |
+
_max_steps_map = {"easy": 30, "medium": 50, "hard": 80}
|
| 132 |
+
if obs.flagged_ids and obs.steps_remaining > 4:
|
| 133 |
+
_steps_used = _max_steps_map.get(obs.task, 50) - obs.steps_remaining
|
| 134 |
+
_n_inv = max(0, (_steps_used - len(obs.inspected_ids)) // 2)
|
| 135 |
+
_n_max = _max_investigate.get(obs.task, 2)
|
| 136 |
+
if _n_inv < min(_n_max, len(obs.flagged_ids)):
|
| 137 |
+
target = obs.flagged_ids[_n_inv] # cycle: 1st flag → 1st investigate, etc.
|
| 138 |
+
return (
|
| 139 |
+
FakeGangAction(action_type=ActionType.INVESTIGATE_NETWORK, account_id=target),
|
| 140 |
+
0.87,
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
# Priority 5 — SUBMIT if fully confident (10 flagged or almost out of steps)
|
| 144 |
+
if len(obs.flagged_ids) >= 10:
|
| 145 |
+
return FakeGangAction(action_type=ActionType.SUBMIT), 0.85
|
| 146 |
+
|
| 147 |
+
if obs.steps_remaining <= 3:
|
| 148 |
+
return FakeGangAction(action_type=ActionType.SUBMIT), 0.90
|
| 149 |
+
|
| 150 |
+
# Priority 6 — INSPECT the highest-risk uninspected account (exploratory)
|
| 151 |
+
uninspected = [i for i in obs.visible_account_ids if i not in obs.inspected_ids]
|
| 152 |
+
if uninspected:
|
| 153 |
+
# Sort by SUSPECT status first, then by insertion order (stable sort).
|
| 154 |
+
# Insertion order = the order accounts entered visible_account_ids:
|
| 155 |
+
# starting_visible accounts come first (added at reset), then accounts
|
| 156 |
+
# revealed by subsequent INSPECTs. This prevents low-ID real accounts
|
| 157 |
+
# from perpetually cutting ahead of gang members that appear early in
|
| 158 |
+
# starting_visible (critical for hard task with 1000-account networks).
|
| 159 |
+
suspects_set = set(obs.suspect_ids)
|
| 160 |
+
uninspected.sort(key=lambda i: (i not in suspects_set,)) # stable → preserves insertion order
|
| 161 |
+
return FakeGangAction(action_type=ActionType.INSPECT, account_id=uninspected[0]), 0.30
|
| 162 |
+
|
| 163 |
+
# Fallback
|
| 164 |
+
return FakeGangAction(action_type=ActionType.SUBMIT), 0.75
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
# ---------------------------------------------------------------------------
|
| 168 |
+
# Alpha computation
|
| 169 |
+
# ---------------------------------------------------------------------------
|
| 170 |
+
|
| 171 |
+
def compute_alpha(recent_win_rate: float, n_reflections: int, task: str = "easy") -> float:
|
| 172 |
+
"""Compute α (LLM trust weight) from recent performance.
|
| 173 |
+
|
| 174 |
+
α = 0.20 + reflection_factor × (0.80 × win_rate + 0.12)
|
| 175 |
+
|
| 176 |
+
reflection_factor ramps from 0 → 1 as reflections accumulate (0–4).
|
| 177 |
+
A reflection bonus of +0.12 × reflection_factor ensures α rises above 0.30
|
| 178 |
+
even with 0% wins after ≥4 reflections. This breaks the chicken-and-egg
|
| 179 |
+
deadlock on medium/hard: without any wins, α was stuck at 0.20 forever,
|
| 180 |
+
meaning the LLM never took exploratory INSPECT steps and could never
|
| 181 |
+
accumulate wins to push α higher.
|
| 182 |
+
|
| 183 |
+
Per-task caps prevent α from climbing so high that the LLM overrides
|
| 184 |
+
correct rule-engine decisions (Priority 2 suspect conf=0.95, FLAG conf=0.90).
|
| 185 |
+
"""
|
| 186 |
+
# Per-task α ceiling: rule engine keeps authority on high-confidence actions
|
| 187 |
+
_alpha_cap = {"easy": 0.50, "medium": 0.70, "hard": 0.85}
|
| 188 |
+
cap = _alpha_cap.get(task, 0.70)
|
| 189 |
+
|
| 190 |
+
reflection_factor = min(1.0, n_reflections / 4.0)
|
| 191 |
+
raw = 0.20 + reflection_factor * (0.80 * recent_win_rate + 0.12)
|
| 192 |
+
return round(max(0.20, min(cap, raw)), 3)
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
# ---------------------------------------------------------------------------
|
| 196 |
+
# Hybrid decision
|
| 197 |
+
# ---------------------------------------------------------------------------
|
| 198 |
+
|
| 199 |
+
def get_hybrid_action(
|
| 200 |
+
obs: FakeGangObservation,
|
| 201 |
+
reflections: List[str],
|
| 202 |
+
few_shot_example: Optional[dict] = None,
|
| 203 |
+
alpha: float = 0.30,
|
| 204 |
+
temperature: float = 0.40,
|
| 205 |
+
) -> Tuple[FakeGangAction, str, str]:
|
| 206 |
+
"""Return the blended action, the raw LLM output, and the decision mode.
|
| 207 |
+
|
| 208 |
+
Decision logic:
|
| 209 |
+
1. Get rule action + confidence
|
| 210 |
+
2. Get LLM action (with Reflexion context)
|
| 211 |
+
3. If they agree → "agree" (unanimous)
|
| 212 |
+
4. If disagree:
|
| 213 |
+
- rule_confidence >= alpha → rule wins ("rule_override")
|
| 214 |
+
- rule_confidence < alpha → LLM wins ("llm")
|
| 215 |
+
|
| 216 |
+
Returns:
|
| 217 |
+
(action, raw_llm_output, mode_str)
|
| 218 |
+
|
| 219 |
+
mode_str is one of:
|
| 220 |
+
"agree" — both said the same thing
|
| 221 |
+
"rule_override(c=X,α=Y)" — rule overrode LLM
|
| 222 |
+
"llm(c=X,α=Y)" — LLM won over rule
|
| 223 |
+
"""
|
| 224 |
+
rule_action, rule_conf = get_rule_action(obs)
|
| 225 |
+
llm_action, raw_llm = get_action(
|
| 226 |
+
obs=obs,
|
| 227 |
+
reflections=reflections,
|
| 228 |
+
few_shot_example=few_shot_example,
|
| 229 |
+
temperature=temperature,
|
| 230 |
+
)
|
| 231 |
+
|
| 232 |
+
agree = (
|
| 233 |
+
rule_action.action_type == llm_action.action_type
|
| 234 |
+
and rule_action.account_id == llm_action.account_id
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
if agree:
|
| 238 |
+
return llm_action, raw_llm, "agree"
|
| 239 |
+
|
| 240 |
+
if rule_conf >= alpha:
|
| 241 |
+
mode = f"rule_override(c={rule_conf:.2f},α={alpha:.2f})"
|
| 242 |
+
return rule_action, raw_llm, mode
|
| 243 |
+
else:
|
| 244 |
+
mode = f"llm(c={rule_conf:.2f}<α={alpha:.2f})"
|
| 245 |
+
return llm_action, raw_llm, mode
|
agent/memory.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Persistent episodic memory for the LLM detective agent.
|
| 2 |
+
|
| 3 |
+
Stores:
|
| 4 |
+
- Reflections: short lessons the agent generates after each episode
|
| 5 |
+
- Best trajectories: full action logs from high-reward episodes (used as few-shot examples)
|
| 6 |
+
|
| 7 |
+
All data is written to disk so learning persists across container restarts
|
| 8 |
+
when the memory/ directory is mounted as a Docker volume.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import json
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
from typing import Any, Dict, List, Optional
|
| 16 |
+
|
| 17 |
+
MEMORY_DIR = Path(__file__).parent.parent / "memory"
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class AgentMemory:
|
| 21 |
+
"""Disk-backed memory for reflections and successful trajectories."""
|
| 22 |
+
|
| 23 |
+
def __init__(self, memory_dir: Path = MEMORY_DIR) -> None:
|
| 24 |
+
self.memory_dir = memory_dir
|
| 25 |
+
self.memory_dir.mkdir(parents=True, exist_ok=True)
|
| 26 |
+
|
| 27 |
+
# ------------------------------------------------------------------
|
| 28 |
+
# Reflections (one JSONL file per task)
|
| 29 |
+
# ------------------------------------------------------------------
|
| 30 |
+
|
| 31 |
+
def _reflections_path(self, task: str) -> Path:
|
| 32 |
+
return self.memory_dir / f"reflections_{task}.jsonl"
|
| 33 |
+
|
| 34 |
+
def add_reflection(
|
| 35 |
+
self,
|
| 36 |
+
task: str,
|
| 37 |
+
text: str,
|
| 38 |
+
episode_num: int,
|
| 39 |
+
reward: float,
|
| 40 |
+
) -> None:
|
| 41 |
+
entry = {
|
| 42 |
+
"episode": episode_num,
|
| 43 |
+
"reward": round(reward, 3),
|
| 44 |
+
"reflection": text.strip(),
|
| 45 |
+
}
|
| 46 |
+
with open(self._reflections_path(task), "a") as f:
|
| 47 |
+
f.write(json.dumps(entry) + "\n")
|
| 48 |
+
|
| 49 |
+
def get_reflections(self, task: str, n: int = 4) -> List[str]:
|
| 50 |
+
"""Return the n most recent reflection texts for a task."""
|
| 51 |
+
path = self._reflections_path(task)
|
| 52 |
+
if not path.exists():
|
| 53 |
+
return []
|
| 54 |
+
lines = path.read_text().strip().splitlines()
|
| 55 |
+
entries = []
|
| 56 |
+
for line in lines:
|
| 57 |
+
try:
|
| 58 |
+
entries.append(json.loads(line))
|
| 59 |
+
except json.JSONDecodeError:
|
| 60 |
+
pass
|
| 61 |
+
# Return the last n reflections
|
| 62 |
+
return [e["reflection"] for e in entries[-n:]]
|
| 63 |
+
|
| 64 |
+
def reflection_count(self, task: str) -> int:
|
| 65 |
+
path = self._reflections_path(task)
|
| 66 |
+
if not path.exists():
|
| 67 |
+
return 0
|
| 68 |
+
return sum(1 for line in path.read_text().splitlines() if line.strip())
|
| 69 |
+
|
| 70 |
+
# ------------------------------------------------------------------
|
| 71 |
+
# Best trajectory (one JSON file per task — stores single best run)
|
| 72 |
+
# ------------------------------------------------------------------
|
| 73 |
+
|
| 74 |
+
def _trajectory_path(self, task: str) -> Path:
|
| 75 |
+
return self.memory_dir / f"best_trajectory_{task}.json"
|
| 76 |
+
|
| 77 |
+
def add_trajectory(
|
| 78 |
+
self,
|
| 79 |
+
task: str,
|
| 80 |
+
action_log: List[str],
|
| 81 |
+
final_message: str,
|
| 82 |
+
reward: float,
|
| 83 |
+
episode_num: int,
|
| 84 |
+
) -> bool:
|
| 85 |
+
"""Save trajectory if it's better than the current best. Returns True if saved."""
|
| 86 |
+
path = self._trajectory_path(task)
|
| 87 |
+
current_best_reward = -999.0
|
| 88 |
+
if path.exists():
|
| 89 |
+
try:
|
| 90 |
+
current_best_reward = json.loads(path.read_text()).get("reward", -999.0)
|
| 91 |
+
except (json.JSONDecodeError, KeyError):
|
| 92 |
+
pass
|
| 93 |
+
|
| 94 |
+
if reward > current_best_reward:
|
| 95 |
+
data = {
|
| 96 |
+
"task": task,
|
| 97 |
+
"episode": episode_num,
|
| 98 |
+
"reward": round(reward, 3),
|
| 99 |
+
"action_log": action_log,
|
| 100 |
+
"final_message": final_message,
|
| 101 |
+
}
|
| 102 |
+
path.write_text(json.dumps(data, indent=2))
|
| 103 |
+
return True
|
| 104 |
+
return False
|
| 105 |
+
|
| 106 |
+
def get_best_trajectory(self, task: str) -> Optional[Dict[str, Any]]:
|
| 107 |
+
"""Return best saved trajectory for task, or None."""
|
| 108 |
+
path = self._trajectory_path(task)
|
| 109 |
+
if not path.exists():
|
| 110 |
+
return None
|
| 111 |
+
try:
|
| 112 |
+
return json.loads(path.read_text())
|
| 113 |
+
except json.JSONDecodeError:
|
| 114 |
+
return None
|
| 115 |
+
|
| 116 |
+
# ------------------------------------------------------------------
|
| 117 |
+
# Win history + alpha persistence
|
| 118 |
+
# ------------------------------------------------------------------
|
| 119 |
+
|
| 120 |
+
def _wins_path(self, task: str) -> Path:
|
| 121 |
+
return self.memory_dir / f"wins_{task}.jsonl"
|
| 122 |
+
|
| 123 |
+
def _alpha_path(self, task: str) -> Path:
|
| 124 |
+
return self.memory_dir / f"alpha_{task}.json"
|
| 125 |
+
|
| 126 |
+
def record_win(self, task: str, won: bool, episode_num: int) -> None:
|
| 127 |
+
"""Append an episode outcome to the win history for this task."""
|
| 128 |
+
entry = {"episode": episode_num, "won": won}
|
| 129 |
+
with open(self._wins_path(task), "a") as f:
|
| 130 |
+
f.write(json.dumps(entry) + "\n")
|
| 131 |
+
|
| 132 |
+
def recent_win_rate(self, task: str, n: int = 10) -> float:
|
| 133 |
+
"""Return win rate over the last n episodes for this task."""
|
| 134 |
+
path = self._wins_path(task)
|
| 135 |
+
if not path.exists():
|
| 136 |
+
return 0.0
|
| 137 |
+
entries = []
|
| 138 |
+
for line in path.read_text().strip().splitlines():
|
| 139 |
+
try:
|
| 140 |
+
entries.append(json.loads(line))
|
| 141 |
+
except json.JSONDecodeError:
|
| 142 |
+
pass
|
| 143 |
+
window = entries[-n:]
|
| 144 |
+
if not window:
|
| 145 |
+
return 0.0
|
| 146 |
+
return sum(1 for e in window if e["won"]) / len(window)
|
| 147 |
+
|
| 148 |
+
def save_alpha(self, task: str, alpha: float) -> None:
|
| 149 |
+
"""Persist the current α (LLM trust weight) for a task."""
|
| 150 |
+
self._alpha_path(task).write_text(json.dumps({"alpha": round(alpha, 3)}))
|
| 151 |
+
|
| 152 |
+
def load_alpha(self, task: str, default: float = 0.20) -> float:
|
| 153 |
+
"""Load persisted α, or return default if not saved yet."""
|
| 154 |
+
path = self._alpha_path(task)
|
| 155 |
+
if not path.exists():
|
| 156 |
+
return default
|
| 157 |
+
try:
|
| 158 |
+
return json.loads(path.read_text()).get("alpha", default)
|
| 159 |
+
except (json.JSONDecodeError, KeyError):
|
| 160 |
+
return default
|
| 161 |
+
|
| 162 |
+
# ------------------------------------------------------------------
|
| 163 |
+
# Summary
|
| 164 |
+
# ------------------------------------------------------------------
|
| 165 |
+
|
| 166 |
+
def summary(self) -> str:
|
| 167 |
+
lines = ["=== Agent Memory ==="]
|
| 168 |
+
for task in ["easy", "medium", "hard"]:
|
| 169 |
+
n_ref = self.reflection_count(task)
|
| 170 |
+
best = self.get_best_trajectory(task)
|
| 171 |
+
best_r = f"{best['reward']:+.2f}" if best else "none"
|
| 172 |
+
alpha = self.load_alpha(task)
|
| 173 |
+
wr = self.recent_win_rate(task, n=10)
|
| 174 |
+
lines.append(
|
| 175 |
+
f" {task:6s}: {n_ref:3d} reflections | best reward: {best_r} "
|
| 176 |
+
f"| α={alpha:.2f} | wr(last10)={wr:.0%}"
|
| 177 |
+
)
|
| 178 |
+
return "\n".join(lines)
|
agent/policy.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""LLM policy for the Fake Gang Detection agent.
|
| 2 |
+
|
| 3 |
+
Uses Qwen3 via AWS Bedrock (configured in bedrock_model.py) to:
|
| 4 |
+
1. Format the current observation into a readable detective briefing
|
| 5 |
+
2. Inject past reflections and a best-trajectory few-shot example
|
| 6 |
+
3. Call Qwen to reason and output the next action
|
| 7 |
+
4. Parse the LLM text output into a typed FakeGangAction
|
| 8 |
+
|
| 9 |
+
Learning mechanism:
|
| 10 |
+
The agent does NOT fine-tune model weights (Bedrock is a black-box API).
|
| 11 |
+
Instead it learns via **Reflexion** — accumulated reflections and successful
|
| 12 |
+
episode demonstrations are injected into every prompt, causing measurably
|
| 13 |
+
better decisions over time.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import re
|
| 19 |
+
import sys
|
| 20 |
+
import time
|
| 21 |
+
from pathlib import Path
|
| 22 |
+
from typing import Dict, List, Optional, Tuple
|
| 23 |
+
|
| 24 |
+
# Resolve imports regardless of working directory
|
| 25 |
+
_ROOT = Path(__file__).parent.parent
|
| 26 |
+
sys.path.insert(0, str(_ROOT))
|
| 27 |
+
sys.path.insert(0, str(_ROOT / "server"))
|
| 28 |
+
|
| 29 |
+
from bedrock_model import invoke_qwen
|
| 30 |
+
from models import FakeGangAction, FakeGangObservation, ActionType
|
| 31 |
+
|
| 32 |
+
# ---------------------------------------------------------------------------
|
| 33 |
+
# System prompt — static, sent as the "system" role to Qwen
|
| 34 |
+
# ---------------------------------------------------------------------------
|
| 35 |
+
|
| 36 |
+
SYSTEM_PROMPT = """\
|
| 37 |
+
You are an elite digital forensics detective. Your mission: identify ALL 10 members \
|
| 38 |
+
of a coordinated fake account gang hidden inside a large social network.
|
| 39 |
+
|
| 40 |
+
━━━ AVAILABLE ACTIONS ━━━
|
| 41 |
+
GET_POLICY → retrieve platform policy + threshold (FREE — call first!)
|
| 42 |
+
INSPECT <account_id> → reveal full profile + follow list (costs 1 step)
|
| 43 |
+
REVERSE_IMAGE_SEARCH <account_id> → reveal photo_reuse_score (costs 1 step)
|
| 44 |
+
ANALYZE_BIO <account_id> → reveal bio_template_score (costs 1 step)
|
| 45 |
+
CHECK_IP <account_id> → reveal ip_cluster_id (costs 2 steps)
|
| 46 |
+
INVESTIGATE_NETWORK <account_id> → expand graph 2 hops, find new IDs (costs 2 steps)
|
| 47 |
+
FLAG <account_id> → mark account as gang member (free)
|
| 48 |
+
UNFLAG <account_id> → remove a flag (free)
|
| 49 |
+
SUBMIT → finalise your answer, end episode
|
| 50 |
+
|
| 51 |
+
━━━ ROUND 2: PLATFORM-ADAPTIVE DETECTION ━━━
|
| 52 |
+
⚠ CRITICAL: Call GET_POLICY first! Each episode runs on a specific platform (Instagram/Snapchat)
|
| 53 |
+
with different detection thresholds and cost models:
|
| 54 |
+
|
| 55 |
+
• Instagram: θ = 0.08 (STRICT) — FP penalty = 0.1 (HIGH precision required)
|
| 56 |
+
Primary signal: photo_reuse (content-focused enforcement)
|
| 57 |
+
Strategy: Only flag when fake_risk >= 0.08 AND primary signals confirmed
|
| 58 |
+
|
| 59 |
+
• Snapchat: θ = 0.74 (LENIENT) — FP penalty = 0.01 (HIGH recall required)
|
| 60 |
+
Primary signal: bio_template (behavior-focused enforcement)
|
| 61 |
+
Strategy: Flag aggressively when fake_risk >= 0.74, maximize recall
|
| 62 |
+
|
| 63 |
+
━━━ HIDDEN SIGNALS (Round 2) ━━━
|
| 64 |
+
⚠ photo_reuse_score, bio_template_score, ip_cluster_id start as 0.0/None!
|
| 65 |
+
You MUST use tool actions to reveal them:
|
| 66 |
+
|
| 67 |
+
• REVERSE_IMAGE_SEARCH → reveals photo_reuse_score (costs 1 step)
|
| 68 |
+
Use when: Instagram platform, or profile looks suspicious but fake_risk borderline
|
| 69 |
+
|
| 70 |
+
• ANALYZE_BIO → reveals bio_template_score (costs 1 step)
|
| 71 |
+
Use when: Snapchat platform, or comment_repeat_score already elevated
|
| 72 |
+
|
| 73 |
+
• CHECK_IP → reveals ip_cluster_id (costs 2 steps — expensive!)
|
| 74 |
+
Use when: Multiple accounts with similar patterns (shared_ip_count > 5)
|
| 75 |
+
|
| 76 |
+
Strategy: Don't blindly call tools on every account. Use tools strategically on:
|
| 77 |
+
1. Accounts with status=SUSPECT (flagged neighbor cascade)
|
| 78 |
+
2. Borderline accounts (fake_risk near platform threshold)
|
| 79 |
+
3. Primary signal for the platform (photo_reuse for Instagram, bio_template for Snapchat)
|
| 80 |
+
|
| 81 |
+
━━━ RISK SCORE GUIDE (platform-adaptive) ━━━
|
| 82 |
+
fake_risk_score computation changes based on platform's primary signal:
|
| 83 |
+
• If photo_reuse is primary → node_risk weighted 0.45 (vs default 0.30)
|
| 84 |
+
• If ip_cluster is primary → behavior_risk weighted 0.40 (vs default 0.25)
|
| 85 |
+
|
| 86 |
+
Thresholds (use platform threshold from GET_POLICY):
|
| 87 |
+
• fake_risk >= θ + 0.25 → CONFIRMED_FAKE status, safe to FLAG
|
| 88 |
+
• θ <= fake_risk < θ + 0.25 → SUSPECT status, investigate with tools first
|
| 89 |
+
• fake_risk < θ → NORMAL status, skip unless connected to gang cluster
|
| 90 |
+
• hub_legitimacy_score > 0.70 → likely celebrity, do NOT flag
|
| 91 |
+
|
| 92 |
+
━━━ RAW SIGNAL THRESHOLDS ━━━
|
| 93 |
+
• photo_reuse_score > 0.5 → stealing celebrity photos (strong on Instagram)
|
| 94 |
+
• bio_template_score > 0.4 → copy-paste bios (strong on Snapchat)
|
| 95 |
+
• comment_repeat_score > 0.6 → copy-paste spam comments
|
| 96 |
+
• shared_ip_count > 5 → sharing IP subnet (hint: use CHECK_IP to confirm cluster)
|
| 97 |
+
• mutual_follow_rate > 0.6 → gang members mutually inflate each other
|
| 98 |
+
• avg_post_hour clustered → all posting in same narrow time window
|
| 99 |
+
|
| 100 |
+
━━━ CORE STRATEGY (Round 2) ━━━
|
| 101 |
+
1. Call GET_POLICY first → learn platform (Instagram/Snapchat) + threshold + primary signal
|
| 102 |
+
2. INSPECT starting accounts → identify high fake_risk_score accounts
|
| 103 |
+
3. Use tool actions strategically:
|
| 104 |
+
- Instagram: REVERSE_IMAGE_SEARCH on borderline suspects
|
| 105 |
+
- Snapchat: ANALYZE_BIO on borderline suspects
|
| 106 |
+
- CHECK_IP only when shared_ip_count > 5 (confirm gang cluster)
|
| 107 |
+
4. INSPECT any status=SUSPECT accounts (cascade from flagged neighbors)
|
| 108 |
+
5. FLAG when fake_risk >= platform threshold + confirmed signals
|
| 109 |
+
6. SUBMIT when you have ~10 flagged OR steps_remaining < 5
|
| 110 |
+
7. Platform bonuses:
|
| 111 |
+
- Instagram: +2.0 reward if precision >= 0.95 (avoid FPs!)
|
| 112 |
+
- Snapchat: +2.0 reward if recall >= 0.95 (catch all fakes!)
|
| 113 |
+
|
| 114 |
+
━━━ RESPONSE FORMAT (always use this exactly) ━━━
|
| 115 |
+
<thinking>
|
| 116 |
+
[your reasoning — platform context, which signals to investigate, which account is most suspicious and why]
|
| 117 |
+
</thinking>
|
| 118 |
+
<action>
|
| 119 |
+
[exactly one action, e.g.: GET_POLICY or INSPECT acc_0042 or REVERSE_IMAGE_SEARCH acc_0007 or SUBMIT]
|
| 120 |
+
</action>\
|
| 121 |
+
"""
|
| 122 |
+
|
| 123 |
+
# ---------------------------------------------------------------------------
|
| 124 |
+
# Observation formatter
|
| 125 |
+
# ---------------------------------------------------------------------------
|
| 126 |
+
|
| 127 |
+
_STATUS_BADGE = {
|
| 128 |
+
"confirmed_fake": "CONFIRMED_FAKE",
|
| 129 |
+
"suspect": "SUSPECT ",
|
| 130 |
+
"normal": "NORMAL ",
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def _format_observation(obs: FakeGangObservation) -> str:
|
| 135 |
+
flagged_str = ", ".join(obs.flagged_ids) if obs.flagged_ids else "none yet"
|
| 136 |
+
uninspected = [i for i in obs.visible_account_ids if i not in obs.inspected_ids]
|
| 137 |
+
suspect_uninspected = [s for s in obs.suspect_ids if s not in obs.inspected_ids]
|
| 138 |
+
|
| 139 |
+
# Round 2: Show platform context if available
|
| 140 |
+
platform_info = ""
|
| 141 |
+
if hasattr(obs, "platform") and obs.platform:
|
| 142 |
+
platform_info = f" | PLATFORM: {obs.platform}"
|
| 143 |
+
|
| 144 |
+
lines = [
|
| 145 |
+
f"TASK: {obs.task.upper()}{platform_info} | Steps remaining: {obs.steps_remaining}",
|
| 146 |
+
f"Evasion triggered: {obs.evasion_triggered} (events so far: {obs.evasion_count})",
|
| 147 |
+
f"Currently flagged ({len(obs.flagged_ids)}/10): {flagged_str}",
|
| 148 |
+
f"Accounts inspected: {len(obs.inspected_ids)} | Not yet inspected: {len(uninspected)}",
|
| 149 |
+
]
|
| 150 |
+
if suspect_uninspected:
|
| 151 |
+
lines.append(
|
| 152 |
+
f"SUSPECTS not yet inspected ({len(suspect_uninspected)}): "
|
| 153 |
+
+ ", ".join(suspect_uninspected[:8])
|
| 154 |
+
+ (f" +{len(suspect_uninspected)-8} more" if len(suspect_uninspected) > 8 else "")
|
| 155 |
+
)
|
| 156 |
+
lines.append("")
|
| 157 |
+
|
| 158 |
+
if obs.visible_accounts:
|
| 159 |
+
lines.append("PROFILED ACCOUNTS (sorted by fake_risk_score — highest first):")
|
| 160 |
+
lines.append(
|
| 161 |
+
" [status | risk | node beh graph hub | photo bio mutual | comment ip_count]"
|
| 162 |
+
)
|
| 163 |
+
scored = sorted(obs.visible_accounts, key=lambda p: p.fake_risk_score, reverse=True)
|
| 164 |
+
for p in scored[:18]:
|
| 165 |
+
status_str = getattr(p.status, "value", str(p.status)) if hasattr(p, "status") else "normal"
|
| 166 |
+
badge = _STATUS_BADGE.get(status_str, "NORMAL ")
|
| 167 |
+
flagged_marker = " ◀ FLAGGED" if p.account_id in obs.flagged_ids else ""
|
| 168 |
+
nc = f" name_chg={p.name_change_count}" if p.name_change_count else ""
|
| 169 |
+
fnbr = f" fnbr={p.flagged_neighbor_count}(!)" if p.flagged_neighbor_count else ""
|
| 170 |
+
hub_warn = " [HUB?]" if p.hub_legitimacy_score > 0.70 else ""
|
| 171 |
+
lines.append(
|
| 172 |
+
f" {badge} {p.account_id}{flagged_marker}: "
|
| 173 |
+
f"risk={p.fake_risk_score:.3f} | "
|
| 174 |
+
f"node={p.node_risk:.2f} beh={p.behavior_risk:.2f} "
|
| 175 |
+
f"graph={p.graph_risk:.2f} hub={p.hub_legitimacy_score:.2f}{hub_warn} | "
|
| 176 |
+
f"photo={p.photo_reuse_score:.3f} bio={p.bio_template_score:.3f} "
|
| 177 |
+
f"mutual={p.mutual_follow_rate:.2f}"
|
| 178 |
+
f"{fnbr}{nc}"
|
| 179 |
+
)
|
| 180 |
+
if len(obs.visible_accounts) > 18:
|
| 181 |
+
lines.append(f" … +{len(obs.visible_accounts) - 18} more inspected accounts")
|
| 182 |
+
else:
|
| 183 |
+
lines.append("No accounts profiled yet — pick one from the known IDs below and INSPECT it.")
|
| 184 |
+
|
| 185 |
+
if uninspected:
|
| 186 |
+
sample = uninspected[:12]
|
| 187 |
+
more = f" +{len(uninspected)-12} more" if len(uninspected) > 12 else ""
|
| 188 |
+
lines.append(f"\nKNOWN UNINSPECTED IDs: {', '.join(sample)}{more}")
|
| 189 |
+
|
| 190 |
+
lines.append(f"\nEnvironment message: {obs.message}")
|
| 191 |
+
return "\n".join(lines)
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def _format_reflections(reflections: List[str]) -> str:
|
| 195 |
+
if not reflections:
|
| 196 |
+
return ""
|
| 197 |
+
parts = ["\n━━━ LESSONS FROM YOUR PAST CASES ━━━"]
|
| 198 |
+
for i, r in enumerate(reflections, 1):
|
| 199 |
+
parts.append(f"{i}. {r.strip()}")
|
| 200 |
+
return "\n".join(parts)
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def _format_few_shot(example: Optional[Dict]) -> str:
|
| 204 |
+
if not example:
|
| 205 |
+
return ""
|
| 206 |
+
log = example.get("action_log", [])
|
| 207 |
+
preview = log[:14]
|
| 208 |
+
tail = f"\n … [{len(log)-14} more steps] …" if len(log) > 14 else ""
|
| 209 |
+
steps_str = "\n".join(f" {s}" for s in preview) + tail
|
| 210 |
+
return (
|
| 211 |
+
f"\n━━━ EXAMPLE SUCCESSFUL CASE (task={example['task']}, reward={example['reward']:+.2f}) ━━━\n"
|
| 212 |
+
f"{steps_str}\n"
|
| 213 |
+
f" → {example['final_message']}"
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
# ---------------------------------------------------------------------------
|
| 218 |
+
# Action parser
|
| 219 |
+
# ---------------------------------------------------------------------------
|
| 220 |
+
|
| 221 |
+
def _parse_action(raw: str, obs: FakeGangObservation) -> FakeGangAction:
|
| 222 |
+
"""Extract a FakeGangAction from the LLM's raw text output."""
|
| 223 |
+
# Prefer content inside <action>…</action>
|
| 224 |
+
m = re.search(r"<action>\s*(.*?)\s*</action>", raw, re.DOTALL | re.IGNORECASE)
|
| 225 |
+
text = m.group(1).strip() if m else raw.strip()
|
| 226 |
+
upper = text.upper()
|
| 227 |
+
|
| 228 |
+
# Round 2: New tool actions (parse before INSPECT to avoid prefix collision)
|
| 229 |
+
if "GET_POLICY" in upper:
|
| 230 |
+
return FakeGangAction(action_type=ActionType.GET_POLICY)
|
| 231 |
+
|
| 232 |
+
if hit := re.search(r"REVERSE_IMAGE_SEARCH\s+(acc_\w+)", upper):
|
| 233 |
+
acc_id = hit.group(1).lower()
|
| 234 |
+
return FakeGangAction(action_type=ActionType.REVERSE_IMAGE_SEARCH, account_id=acc_id)
|
| 235 |
+
|
| 236 |
+
if hit := re.search(r"ANALYZE_BIO\s+(acc_\w+)", upper):
|
| 237 |
+
acc_id = hit.group(1).lower()
|
| 238 |
+
return FakeGangAction(action_type=ActionType.ANALYZE_BIO, account_id=acc_id)
|
| 239 |
+
|
| 240 |
+
if hit := re.search(r"CHECK_IP\s+(acc_\w+)", upper):
|
| 241 |
+
acc_id = hit.group(1).lower()
|
| 242 |
+
return FakeGangAction(action_type=ActionType.CHECK_IP, account_id=acc_id)
|
| 243 |
+
|
| 244 |
+
# INVESTIGATE_NETWORK must come before INSPECT (it's a longer prefix)
|
| 245 |
+
if hit := re.search(r"INVESTIGATE_NETWORK\s+(acc_\w+)", upper):
|
| 246 |
+
acc_id = hit.group(1).lower()
|
| 247 |
+
if acc_id in obs.visible_account_ids:
|
| 248 |
+
return FakeGangAction(action_type=ActionType.INVESTIGATE_NETWORK, account_id=acc_id)
|
| 249 |
+
|
| 250 |
+
if hit := re.search(r"INSPECT\s+(acc_\w+)", upper):
|
| 251 |
+
acc_id = hit.group(1).lower()
|
| 252 |
+
if acc_id in obs.visible_account_ids:
|
| 253 |
+
return FakeGangAction(action_type=ActionType.INSPECT, account_id=acc_id)
|
| 254 |
+
|
| 255 |
+
if hit := re.search(r"UNFLAG\s+(acc_\w+)", upper):
|
| 256 |
+
return FakeGangAction(action_type=ActionType.UNFLAG, account_id=hit.group(1).lower())
|
| 257 |
+
|
| 258 |
+
if hit := re.search(r"FLAG\s+(acc_\w+)", upper):
|
| 259 |
+
return FakeGangAction(action_type=ActionType.FLAG, account_id=hit.group(1).lower())
|
| 260 |
+
|
| 261 |
+
if "SUBMIT" in upper:
|
| 262 |
+
return FakeGangAction(action_type=ActionType.SUBMIT)
|
| 263 |
+
|
| 264 |
+
# Fallback: inspect the highest-scored uninspected account
|
| 265 |
+
uninspected = [i for i in obs.visible_account_ids if i not in obs.inspected_ids]
|
| 266 |
+
if uninspected:
|
| 267 |
+
return FakeGangAction(action_type=ActionType.INSPECT, account_id=uninspected[0])
|
| 268 |
+
return FakeGangAction(action_type=ActionType.SUBMIT)
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
# ---------------------------------------------------------------------------
|
| 272 |
+
# Public interface
|
| 273 |
+
# ---------------------------------------------------------------------------
|
| 274 |
+
|
| 275 |
+
def get_action(
|
| 276 |
+
obs: FakeGangObservation,
|
| 277 |
+
reflections: List[str],
|
| 278 |
+
few_shot_example: Optional[Dict] = None,
|
| 279 |
+
temperature: float = 0.4,
|
| 280 |
+
max_retries: int = 3,
|
| 281 |
+
) -> Tuple[FakeGangAction, str]:
|
| 282 |
+
"""
|
| 283 |
+
Query Qwen3 (via AWS Bedrock) for the next detective action.
|
| 284 |
+
|
| 285 |
+
Returns:
|
| 286 |
+
(action, raw_llm_output)
|
| 287 |
+
|
| 288 |
+
Learning signal flows in through `reflections` and `few_shot_example`:
|
| 289 |
+
- reflections: list of past post-episode lessons (from agent/reflection.py)
|
| 290 |
+
- few_shot_example: best saved trajectory (from agent/memory.py)
|
| 291 |
+
Both grow richer over training, causing measurably better decisions.
|
| 292 |
+
"""
|
| 293 |
+
reflections_text = _format_reflections(reflections)
|
| 294 |
+
few_shot_text = _format_few_shot(few_shot_example)
|
| 295 |
+
obs_text = _format_observation(obs)
|
| 296 |
+
|
| 297 |
+
prompt = (
|
| 298 |
+
f"{reflections_text}"
|
| 299 |
+
f"{few_shot_text}"
|
| 300 |
+
f"\n\n━━━ CURRENT CASE ━━━\n"
|
| 301 |
+
f"{obs_text}"
|
| 302 |
+
f"\n\nWhat is your next action?"
|
| 303 |
+
)
|
| 304 |
+
|
| 305 |
+
last_error: Optional[Exception] = None
|
| 306 |
+
for attempt in range(max_retries):
|
| 307 |
+
try:
|
| 308 |
+
raw = invoke_qwen(
|
| 309 |
+
prompt=prompt,
|
| 310 |
+
system=SYSTEM_PROMPT,
|
| 311 |
+
max_tokens=512,
|
| 312 |
+
temperature=temperature,
|
| 313 |
+
)
|
| 314 |
+
action = _parse_action(raw, obs)
|
| 315 |
+
return action, raw
|
| 316 |
+
except Exception as exc:
|
| 317 |
+
last_error = exc
|
| 318 |
+
wait = 2 ** attempt
|
| 319 |
+
print(f" [policy] Bedrock call failed (attempt {attempt+1}): {exc} — retrying in {wait}s")
|
| 320 |
+
time.sleep(wait)
|
| 321 |
+
|
| 322 |
+
# All retries exhausted — fall back to heuristic
|
| 323 |
+
print(f" [policy] All retries exhausted: {last_error}. Using heuristic fallback.")
|
| 324 |
+
return _heuristic_fallback(obs), "[FALLBACK — Bedrock unavailable]"
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def _heuristic_fallback(obs: FakeGangObservation) -> FakeGangAction:
|
| 328 |
+
"""Simple heuristic used when Bedrock is unavailable."""
|
| 329 |
+
uninspected = [i for i in obs.visible_account_ids if i not in obs.inspected_ids]
|
| 330 |
+
|
| 331 |
+
if uninspected and obs.steps_remaining > 3:
|
| 332 |
+
return FakeGangAction(action_type=ActionType.INSPECT, account_id=uninspected[0])
|
| 333 |
+
|
| 334 |
+
# Flag high-signal accounts
|
| 335 |
+
for p in obs.visible_accounts:
|
| 336 |
+
if (p.photo_reuse_score > 0.5 and p.bio_template_score > 0.4
|
| 337 |
+
and p.account_id not in obs.flagged_ids):
|
| 338 |
+
return FakeGangAction(action_type=ActionType.FLAG, account_id=p.account_id)
|
| 339 |
+
|
| 340 |
+
return FakeGangAction(action_type=ActionType.SUBMIT)
|
agent/reflection.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Post-episode reflection generator.
|
| 2 |
+
|
| 3 |
+
After each episode the agent calls `generate_reflection()`.
|
| 4 |
+
Qwen3 analyses the action log and outcome, then writes a concrete lesson
|
| 5 |
+
that gets stored in AgentMemory and injected into future prompts.
|
| 6 |
+
|
| 7 |
+
This is the core **learning mechanism**:
|
| 8 |
+
Episode N fails → reflection generated → stored in memory
|
| 9 |
+
Episode N+1 → reflection in prompt → agent avoids past mistake
|
| 10 |
+
...
|
| 11 |
+
Win rate rises measurably over episodes.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import sys
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
from typing import Any, Dict, List
|
| 19 |
+
|
| 20 |
+
_ROOT = Path(__file__).parent.parent
|
| 21 |
+
sys.path.insert(0, str(_ROOT))
|
| 22 |
+
|
| 23 |
+
from bedrock_model import invoke_qwen
|
| 24 |
+
|
| 25 |
+
# ---------------------------------------------------------------------------
|
| 26 |
+
# Reflection prompt
|
| 27 |
+
# ---------------------------------------------------------------------------
|
| 28 |
+
|
| 29 |
+
_REFLECTION_SYSTEM = """\
|
| 30 |
+
You are a senior detective reviewing a FAKE INSTAGRAM ACCOUNT detection case debrief.
|
| 31 |
+
This environment detects coordinated fake social media accounts — NOT financial fraud.
|
| 32 |
+
|
| 33 |
+
Signals available in this environment (use ONLY these):
|
| 34 |
+
• comment_repeat_score > 0.6 → copy-paste spam comments (gang: 0.6-0.9, real: 0.0-0.08)
|
| 35 |
+
• shared_ip_count > 5 → shares IP subnet (all 10 gang members have count=9)
|
| 36 |
+
• photo_reuse_score > 0.5 → stolen profile photos
|
| 37 |
+
• bio_template_score > 0.4 → copy-paste bio text
|
| 38 |
+
• fake_risk_score > 0.75 → high-confidence gang member (composite score)
|
| 39 |
+
• hub_legitimacy_score > 0.70 → celebrity account, do NOT flag
|
| 40 |
+
• After FLAG: visible neighbors auto-become SUSPECT (priority targets)
|
| 41 |
+
|
| 42 |
+
Available actions: INSPECT (1 step, reveals profile), INVESTIGATE_NETWORK (2 steps, 2-hop expand),
|
| 43 |
+
FLAG, UNFLAG, SUBMIT.
|
| 44 |
+
|
| 45 |
+
CRITICAL: Write lessons about fake social media signals and INSPECT/INVESTIGATE_NETWORK strategy
|
| 46 |
+
ONLY. Do NOT mention transactions, financial transfers, banking, or any concepts not listed above.
|
| 47 |
+
Output only the lesson text — no headers, no bullet points, just 2-3 plain sentences.\
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def generate_reflection(
|
| 52 |
+
task: str,
|
| 53 |
+
action_log: List[str],
|
| 54 |
+
final_message: str,
|
| 55 |
+
won: bool,
|
| 56 |
+
steps_used: int,
|
| 57 |
+
max_steps: int,
|
| 58 |
+
episode_num: int,
|
| 59 |
+
) -> str:
|
| 60 |
+
"""
|
| 61 |
+
Ask Qwen3 to generate a concrete lesson from one completed episode.
|
| 62 |
+
Returns a short string (2-3 sentences) suitable for memory storage.
|
| 63 |
+
"""
|
| 64 |
+
outcome = "SUCCESS" if won else "FAILURE"
|
| 65 |
+
log_preview = "\n".join(f" {i+1}. {s}" for i, s in enumerate(action_log[:20]))
|
| 66 |
+
if len(action_log) > 20:
|
| 67 |
+
log_preview += f"\n … [{len(action_log) - 20} more steps]"
|
| 68 |
+
|
| 69 |
+
prompt = f"""\
|
| 70 |
+
FAKE INSTAGRAM ACCOUNT DETECTION — Episode {episode_num}
|
| 71 |
+
Task difficulty: {task.upper()}
|
| 72 |
+
Outcome: {outcome}
|
| 73 |
+
Steps used: {steps_used}/{max_steps}
|
| 74 |
+
Result: {final_message}
|
| 75 |
+
|
| 76 |
+
AVAILABLE SIGNALS (reference for your lesson):
|
| 77 |
+
comment_repeat_score > 0.6 | shared_ip_count > 5 | photo_reuse_score > 0.5
|
| 78 |
+
fake_risk_score > 0.75 | hub_legitimacy_score > 0.70 (celebrity, skip)
|
| 79 |
+
After FLAG → neighbors become SUSPECT (inspect them immediately)
|
| 80 |
+
INVESTIGATE_NETWORK on a flagged account reveals their 2-hop gang cluster
|
| 81 |
+
|
| 82 |
+
INVESTIGATION LOG:
|
| 83 |
+
{log_preview}
|
| 84 |
+
|
| 85 |
+
Write a 2-3 sentence lesson for your future self based on this case.
|
| 86 |
+
Focus on: which of the above signals were most diagnostic, whether using
|
| 87 |
+
INVESTIGATE_NETWORK after the first FLAG would have helped, and how to
|
| 88 |
+
better allocate the step budget. Be concrete and actionable.\
|
| 89 |
+
"""
|
| 90 |
+
|
| 91 |
+
try:
|
| 92 |
+
reflection = invoke_qwen(
|
| 93 |
+
prompt=prompt,
|
| 94 |
+
system=_REFLECTION_SYSTEM,
|
| 95 |
+
max_tokens=180,
|
| 96 |
+
temperature=0.6,
|
| 97 |
+
)
|
| 98 |
+
return reflection.strip()
|
| 99 |
+
except Exception as exc:
|
| 100 |
+
# If Bedrock fails, generate a minimal rule-based reflection
|
| 101 |
+
return _rule_based_reflection(won, steps_used, max_steps, final_message)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _rule_based_reflection(
|
| 105 |
+
won: bool, steps_used: int, max_steps: int, final_message: str
|
| 106 |
+
) -> str:
|
| 107 |
+
"""Minimal fallback reflection when Bedrock is unavailable."""
|
| 108 |
+
if won and steps_used < max_steps * 0.6:
|
| 109 |
+
return (
|
| 110 |
+
"Early INVESTIGATE_NETWORK calls efficiently expanded the graph to all gang members. "
|
| 111 |
+
"Flagging accounts with both high photo_reuse AND bio_template scores maintained precision. "
|
| 112 |
+
"Submitting with budget remaining earned an efficiency bonus."
|
| 113 |
+
)
|
| 114 |
+
if won:
|
| 115 |
+
return (
|
| 116 |
+
"Found all gang members but used most of the step budget. "
|
| 117 |
+
"Look for intra-gang follow density earlier — once you find one member, INVESTIGATE_NETWORK immediately. "
|
| 118 |
+
"Flag faster to leave budget for verification."
|
| 119 |
+
)
|
| 120 |
+
if "Recall=0.00" in final_message or "TP=0" in final_message:
|
| 121 |
+
return (
|
| 122 |
+
"Zero gang members found — the starting accounts were all real. "
|
| 123 |
+
"After inspecting 3-4 low-signal accounts, use INVESTIGATE_NETWORK to jump to a different part of the graph. "
|
| 124 |
+
"Gang members have photo_reuse > 0.5 and bio_template > 0.4 simultaneously."
|
| 125 |
+
)
|
| 126 |
+
return (
|
| 127 |
+
"Partial recall — found some gang members but missed others. "
|
| 128 |
+
"After flagging the first gang member, immediately use INVESTIGATE_NETWORK: gang members follow each other heavily. "
|
| 129 |
+
"Don't waste steps inspecting low-signal accounts one-by-one."
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
# ---------------------------------------------------------------------------
|
| 134 |
+
# Post-win reflection (reinforces what worked)
|
| 135 |
+
# ---------------------------------------------------------------------------
|
| 136 |
+
|
| 137 |
+
def generate_success_reflection(
|
| 138 |
+
task: str,
|
| 139 |
+
action_log: List[str],
|
| 140 |
+
final_message: str,
|
| 141 |
+
steps_used: int,
|
| 142 |
+
max_steps: int,
|
| 143 |
+
episode_num: int,
|
| 144 |
+
) -> str:
|
| 145 |
+
"""Generate a reinforcement reflection after a WIN to capture what worked."""
|
| 146 |
+
log_preview = "\n".join(f" {i+1}. {s}" for i, s in enumerate(action_log[:15]))
|
| 147 |
+
|
| 148 |
+
prompt = f"""\
|
| 149 |
+
SUCCESSFUL CASE — Episode {episode_num}
|
| 150 |
+
Task: {task.upper()} | Steps used: {steps_used}/{max_steps}
|
| 151 |
+
Result: {final_message}
|
| 152 |
+
|
| 153 |
+
INVESTIGATION LOG (first 15 steps):
|
| 154 |
+
{log_preview}
|
| 155 |
+
|
| 156 |
+
In 2-3 sentences, describe the specific strategy that led to success.
|
| 157 |
+
What did you do right? What should you repeat in future cases?\
|
| 158 |
+
"""
|
| 159 |
+
|
| 160 |
+
try:
|
| 161 |
+
return invoke_qwen(
|
| 162 |
+
prompt=prompt,
|
| 163 |
+
system=_REFLECTION_SYSTEM,
|
| 164 |
+
max_tokens=150,
|
| 165 |
+
temperature=0.5,
|
| 166 |
+
).strip()
|
| 167 |
+
except Exception:
|
| 168 |
+
return _rule_based_reflection(True, steps_used, max_steps, final_message)
|
agent/train.py
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Training script for Fake Gang Detection agent.
|
| 4 |
+
|
| 5 |
+
Supports:
|
| 6 |
+
- AWS Bedrock models (Qwen, Claude, Llama)
|
| 7 |
+
- HuggingFace router models
|
| 8 |
+
- Local rule-based baseline
|
| 9 |
+
- Platform-specific training (Instagram/Snapchat)
|
| 10 |
+
- Metrics tracking and visualization
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import argparse
|
| 14 |
+
import json
|
| 15 |
+
import os
|
| 16 |
+
import sys
|
| 17 |
+
import time
|
| 18 |
+
from datetime import datetime
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
from typing import Dict, List, Optional, Tuple
|
| 21 |
+
|
| 22 |
+
# Add parent to path
|
| 23 |
+
_ROOT = Path(__file__).parent.parent
|
| 24 |
+
sys.path.insert(0, str(_ROOT))
|
| 25 |
+
|
| 26 |
+
from server.environment import FakeGangEnvironment
|
| 27 |
+
from models import FakeGangAction, FakeGangObservation, ActionType
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# ============================================================================
|
| 31 |
+
# LLM Backends
|
| 32 |
+
# ============================================================================
|
| 33 |
+
|
| 34 |
+
def call_bedrock(prompt: str, system_prompt: str, model_id: str) -> str:
|
| 35 |
+
"""Call AWS Bedrock model."""
|
| 36 |
+
import boto3
|
| 37 |
+
|
| 38 |
+
client = boto3.client(
|
| 39 |
+
service_name="bedrock-runtime",
|
| 40 |
+
region_name=os.getenv("AWS_DEFAULT_REGION", "us-east-1"),
|
| 41 |
+
aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"),
|
| 42 |
+
aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"),
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
# Try converse API first (boto3 >= 1.34.x)
|
| 46 |
+
if hasattr(client, "converse"):
|
| 47 |
+
resp = client.converse(
|
| 48 |
+
modelId=model_id,
|
| 49 |
+
messages=[{"role": "user", "content": [{"text": prompt}]}],
|
| 50 |
+
system=[{"text": system_prompt}],
|
| 51 |
+
inferenceConfig={"temperature": 0.3, "maxTokens": 512},
|
| 52 |
+
)
|
| 53 |
+
return resp["output"]["message"]["content"][0]["text"]
|
| 54 |
+
else:
|
| 55 |
+
# Fallback to invoke_model
|
| 56 |
+
body = json.dumps({
|
| 57 |
+
"prompt": f"{system_prompt}\n\nUser: {prompt}\n\nAssistant:",
|
| 58 |
+
"max_tokens": 512,
|
| 59 |
+
"temperature": 0.3,
|
| 60 |
+
})
|
| 61 |
+
resp = client.invoke_model(modelId=model_id, body=body)
|
| 62 |
+
result = json.loads(resp["body"].read())
|
| 63 |
+
return result.get("completion", result.get("content", [{"text": "SUBMIT"}])[0]["text"])
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def call_hf(prompt: str, system_prompt: str, model_name: str, api_key: str) -> str:
|
| 67 |
+
"""Call HuggingFace router model."""
|
| 68 |
+
from openai import OpenAI
|
| 69 |
+
|
| 70 |
+
client = OpenAI(
|
| 71 |
+
base_url=os.getenv("API_BASE_URL", "https://router.huggingface.co/v1"),
|
| 72 |
+
api_key=api_key,
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
resp = client.chat.completions.create(
|
| 76 |
+
model=model_name,
|
| 77 |
+
messages=[
|
| 78 |
+
{"role": "system", "content": system_prompt},
|
| 79 |
+
{"role": "user", "content": prompt},
|
| 80 |
+
],
|
| 81 |
+
temperature=0.3,
|
| 82 |
+
max_tokens=512,
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
return (resp.choices[0].message.content or "").strip()
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
# ============================================================================
|
| 89 |
+
# System Prompt (Round 2)
|
| 90 |
+
# ============================================================================
|
| 91 |
+
|
| 92 |
+
SYSTEM_PROMPT = """You are an AI detective finding 10 coordinated fake accounts in a social network.
|
| 93 |
+
|
| 94 |
+
━━━ ROUND 2: PLATFORM-ADAPTIVE DETECTION ━━━
|
| 95 |
+
Episodes run on specific platforms (Instagram/Snapchat) with different thresholds and enforcement priorities.
|
| 96 |
+
|
| 97 |
+
ACTIONS (reply with exactly ONE line):
|
| 98 |
+
- GET_POLICY — get platform policy (FREE, call first!)
|
| 99 |
+
- INSPECT acc_XXXX — reveal profile (costs 1 step)
|
| 100 |
+
- REVERSE_IMAGE_SEARCH acc_XXXX — reveal photo_reuse_score (costs 1 step)
|
| 101 |
+
- ANALYZE_BIO acc_XXXX — reveal bio_template_score (costs 1 step)
|
| 102 |
+
- CHECK_IP acc_XXXX — reveal ip_cluster_id (costs 2 steps, expensive!)
|
| 103 |
+
- FLAG acc_XXXX — mark as fake (FREE, no step cost, triggers suspect cascade)
|
| 104 |
+
- SUBMIT — end episode, get scored
|
| 105 |
+
|
| 106 |
+
DECISION RULES (Round 2, apply top-to-bottom):
|
| 107 |
+
1. First action of episode → GET_POLICY (learn platform threshold and primary signal)
|
| 108 |
+
2. If suspect_ids lists accounts you haven't inspected → INSPECT the first one
|
| 109 |
+
3. If ANY profiled account has shared_ip_count >= 5 → CHECK_IP to confirm cluster, then FLAG
|
| 110 |
+
4. If photo_reuse_score or bio_template_score is 0.0 on suspicious accounts → use REVERSE_IMAGE_SEARCH or ANALYZE_BIO
|
| 111 |
+
5. If ANY profiled account has photo_reuse >= 0.50 AND bio_template >= 0.40 and hub < 0.70 → FLAG
|
| 112 |
+
6. If fake_risk_score >= platform_threshold and hub < 0.70 → FLAG
|
| 113 |
+
7. If uninspected visible accounts and steps > 3 → INSPECT the next one
|
| 114 |
+
8. If you have 10 flags OR steps <= 3 → SUBMIT
|
| 115 |
+
|
| 116 |
+
PLATFORM-SPECIFIC STRATEGIES:
|
| 117 |
+
- Instagram (threshold ~0.08, high FP penalty): Be precise, use REVERSE_IMAGE_SEARCH on borderline cases
|
| 118 |
+
- Snapchat (threshold ~0.74, low FP penalty): Be aggressive, flag when fake_risk >= 0.74
|
| 119 |
+
|
| 120 |
+
IMPORTANT:
|
| 121 |
+
- Hidden signals (photo_reuse, bio_template, ip_cluster) start as 0.0/None — use tools to reveal!
|
| 122 |
+
- GET_POLICY is FREE and shows platform threshold — always call first
|
| 123 |
+
- FLAG is FREE (costs 0 steps) — flag aggressively when you see suspicious signals
|
| 124 |
+
- CHECK_IP costs 2 steps (expensive) — only use when shared_ip_count >= 5
|
| 125 |
+
- hub_legitimacy_score > 0.70 means celebrity — do NOT flag
|
| 126 |
+
|
| 127 |
+
Reply with EXACTLY one line, nothing else:
|
| 128 |
+
GET_POLICY
|
| 129 |
+
REVERSE_IMAGE_SEARCH acc_XXXX
|
| 130 |
+
ANALYZE_BIO acc_XXXX
|
| 131 |
+
CHECK_IP acc_XXXX
|
| 132 |
+
FLAG acc_XXXX
|
| 133 |
+
INSPECT acc_XXXX
|
| 134 |
+
SUBMIT"""
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
# ============================================================================
|
| 138 |
+
# Agent Policy
|
| 139 |
+
# ============================================================================
|
| 140 |
+
|
| 141 |
+
def format_observation(obs: FakeGangObservation) -> str:
|
| 142 |
+
"""Format observation as text prompt for LLM."""
|
| 143 |
+
lines = []
|
| 144 |
+
|
| 145 |
+
# Platform context
|
| 146 |
+
platform_info = f" | PLATFORM: {obs.platform}" if obs.platform else ""
|
| 147 |
+
lines.append(f"TASK: {obs.task.upper()}{platform_info} | Steps remaining: {obs.steps_remaining}")
|
| 148 |
+
|
| 149 |
+
flagged = obs.flagged_ids
|
| 150 |
+
lines.append(f"Flagged ({len(flagged)}/10): {', '.join(flagged) if flagged else 'none'}")
|
| 151 |
+
|
| 152 |
+
# Suspects (high priority)
|
| 153 |
+
suspects = obs.suspect_ids
|
| 154 |
+
inspected = obs.inspected_ids
|
| 155 |
+
uninspected_suspects = [s for s in suspects if s not in inspected]
|
| 156 |
+
if uninspected_suspects:
|
| 157 |
+
lines.append(f"*** SUSPECTS (uninspected) → INSPECT THESE FIRST: {', '.join(uninspected_suspects)} ***")
|
| 158 |
+
|
| 159 |
+
# Accounts
|
| 160 |
+
if obs.visible_accounts:
|
| 161 |
+
unflagged_suspicious = []
|
| 162 |
+
flagged_accs = []
|
| 163 |
+
clean_accs = []
|
| 164 |
+
|
| 165 |
+
for a in sorted(obs.visible_accounts, key=lambda x: x.fake_risk_score, reverse=True):
|
| 166 |
+
aid = a.account_id
|
| 167 |
+
if aid in flagged:
|
| 168 |
+
flagged_accs.append(a)
|
| 169 |
+
elif (a.shared_ip_count >= 5 or
|
| 170 |
+
(a.photo_reuse_score >= 0.50 and a.bio_template_score >= 0.40)):
|
| 171 |
+
unflagged_suspicious.append(a)
|
| 172 |
+
else:
|
| 173 |
+
clean_accs.append(a)
|
| 174 |
+
|
| 175 |
+
if unflagged_suspicious:
|
| 176 |
+
lines.append(f"\n!!! ACTION NEEDED — FLAG THESE ({len(unflagged_suspicious)} suspicious):")
|
| 177 |
+
for a in unflagged_suspicious:
|
| 178 |
+
lines.append(f" → FLAG {a.account_id}: risk={a.fake_risk_score:.3f} photo={a.photo_reuse_score:.2f} bio={a.bio_template_score:.2f} ip_shared={a.shared_ip_count} hub={a.hub_legitimacy_score:.2f}")
|
| 179 |
+
|
| 180 |
+
if flagged_accs:
|
| 181 |
+
lines.append(f"\nALREADY FLAGGED ({len(flagged_accs)}):")
|
| 182 |
+
for a in flagged_accs[:5]:
|
| 183 |
+
lines.append(f" ✓ {a.account_id}")
|
| 184 |
+
|
| 185 |
+
if clean_accs:
|
| 186 |
+
lines.append(f"\nCLEAN ({len(clean_accs)}):")
|
| 187 |
+
for a in clean_accs[:8]:
|
| 188 |
+
hub_mark = " [CELEBRITY]" if a.hub_legitimacy_score > 0.70 else ""
|
| 189 |
+
lines.append(f" {a.account_id}: risk={a.fake_risk_score:.3f} photo={a.photo_reuse_score:.2f} bio={a.bio_template_score:.2f} hub={a.hub_legitimacy_score:.2f}{hub_mark}")
|
| 190 |
+
|
| 191 |
+
visible_ids = obs.visible_account_ids
|
| 192 |
+
uninspected_ids = [i for i in visible_ids if i not in inspected]
|
| 193 |
+
if uninspected_ids:
|
| 194 |
+
lines.append(f"\nUninspected IDs ({len(uninspected_ids)}): {', '.join(uninspected_ids[:10])}{'...' if len(uninspected_ids) > 10 else ''}")
|
| 195 |
+
|
| 196 |
+
lines.append(f"\nMessage: {obs.message}")
|
| 197 |
+
return "\n".join(lines)
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def parse_action(text: str, obs: FakeGangObservation) -> FakeGangAction:
|
| 201 |
+
"""Parse LLM response into action."""
|
| 202 |
+
text = text.strip().upper()
|
| 203 |
+
|
| 204 |
+
for line in text.split("\n"):
|
| 205 |
+
line = line.strip()
|
| 206 |
+
parts = line.split(maxsplit=1)
|
| 207 |
+
verb = parts[0]
|
| 208 |
+
acc = parts[1].lower() if len(parts) > 1 else None
|
| 209 |
+
|
| 210 |
+
# Round 2 actions
|
| 211 |
+
if verb == "GET_POLICY":
|
| 212 |
+
return FakeGangAction(action_type=ActionType.GET_POLICY)
|
| 213 |
+
if verb == "REVERSE_IMAGE_SEARCH" and acc:
|
| 214 |
+
return FakeGangAction(action_type=ActionType.REVERSE_IMAGE_SEARCH, account_id=acc)
|
| 215 |
+
if verb == "ANALYZE_BIO" and acc:
|
| 216 |
+
return FakeGangAction(action_type=ActionType.ANALYZE_BIO, account_id=acc)
|
| 217 |
+
if verb == "CHECK_IP" and acc:
|
| 218 |
+
return FakeGangAction(action_type=ActionType.CHECK_IP, account_id=acc)
|
| 219 |
+
|
| 220 |
+
# Round 1 actions
|
| 221 |
+
if verb in ("INSPECT", "FLAG", "UNFLAG", "INVESTIGATE_NETWORK"):
|
| 222 |
+
if acc:
|
| 223 |
+
return FakeGangAction(action_type=ActionType[verb], account_id=acc)
|
| 224 |
+
if verb == "SUBMIT":
|
| 225 |
+
return FakeGangAction(action_type=ActionType.SUBMIT)
|
| 226 |
+
|
| 227 |
+
# Fallback: inspect first uninspected
|
| 228 |
+
for s in obs.suspect_ids:
|
| 229 |
+
if s not in obs.inspected_ids:
|
| 230 |
+
return FakeGangAction(action_type=ActionType.INSPECT, account_id=s)
|
| 231 |
+
|
| 232 |
+
for v in obs.visible_account_ids:
|
| 233 |
+
if v not in obs.inspected_ids:
|
| 234 |
+
return FakeGangAction(action_type=ActionType.INSPECT, account_id=v)
|
| 235 |
+
|
| 236 |
+
return FakeGangAction(action_type=ActionType.SUBMIT)
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
# ============================================================================
|
| 240 |
+
# Episode Runner
|
| 241 |
+
# ============================================================================
|
| 242 |
+
|
| 243 |
+
def run_episode(
|
| 244 |
+
env: FakeGangEnvironment,
|
| 245 |
+
task: str,
|
| 246 |
+
seed: int,
|
| 247 |
+
backend: str,
|
| 248 |
+
model_id: str,
|
| 249 |
+
verbose: bool = False,
|
| 250 |
+
) -> Dict:
|
| 251 |
+
"""Run one episode and return metrics."""
|
| 252 |
+
|
| 253 |
+
obs = env.reset(task=task, seed=seed)
|
| 254 |
+
platform = obs.platform
|
| 255 |
+
|
| 256 |
+
actions_taken = []
|
| 257 |
+
tool_counts = {
|
| 258 |
+
"GET_POLICY": 0,
|
| 259 |
+
"REVERSE_IMAGE_SEARCH": 0,
|
| 260 |
+
"ANALYZE_BIO": 0,
|
| 261 |
+
"CHECK_IP": 0,
|
| 262 |
+
"INSPECT": 0,
|
| 263 |
+
"FLAG": 0,
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
start_time = time.time()
|
| 267 |
+
|
| 268 |
+
while not obs.done:
|
| 269 |
+
# Format observation
|
| 270 |
+
prompt = format_observation(obs)
|
| 271 |
+
|
| 272 |
+
# Get action from LLM or rule-based
|
| 273 |
+
if backend == "bedrock":
|
| 274 |
+
response = call_bedrock(prompt, SYSTEM_PROMPT, model_id)
|
| 275 |
+
action = parse_action(response, obs)
|
| 276 |
+
elif backend == "hf":
|
| 277 |
+
api_key = os.getenv("HF_TOKEN") or os.getenv("API_KEY")
|
| 278 |
+
response = call_hf(prompt, SYSTEM_PROMPT, model_id, api_key)
|
| 279 |
+
action = parse_action(response, obs)
|
| 280 |
+
else: # rule-based
|
| 281 |
+
action = get_rule_based_action(obs)
|
| 282 |
+
|
| 283 |
+
# Track action
|
| 284 |
+
actions_taken.append(action.action_type.value)
|
| 285 |
+
if action.action_type.value.upper() in tool_counts:
|
| 286 |
+
tool_counts[action.action_type.value.upper()] += 1
|
| 287 |
+
|
| 288 |
+
# Step environment
|
| 289 |
+
obs = env.step(action)
|
| 290 |
+
|
| 291 |
+
if verbose:
|
| 292 |
+
print(f" [{obs.steps_remaining:2d}] {action.action_type.value:20s} {action.account_id or ''}")
|
| 293 |
+
|
| 294 |
+
elapsed = time.time() - start_time
|
| 295 |
+
|
| 296 |
+
# Get final metrics
|
| 297 |
+
grader = env._last_grader_score
|
| 298 |
+
|
| 299 |
+
# Count TP/FP/FN
|
| 300 |
+
flagged = set(env._flagged)
|
| 301 |
+
gang_members = set(env._ep["gang_member_ids"])
|
| 302 |
+
tp = len(flagged & gang_members)
|
| 303 |
+
fp = len(flagged - gang_members)
|
| 304 |
+
fn = len(gang_members - flagged)
|
| 305 |
+
|
| 306 |
+
precision = tp / max(tp + fp, 1)
|
| 307 |
+
recall = tp / max(tp + fn, 1)
|
| 308 |
+
|
| 309 |
+
return {
|
| 310 |
+
"episode": seed,
|
| 311 |
+
"platform": platform,
|
| 312 |
+
"task": task,
|
| 313 |
+
"reward": obs.reward or 0.0,
|
| 314 |
+
"grader_score": grader,
|
| 315 |
+
"tp": tp,
|
| 316 |
+
"fp": fp,
|
| 317 |
+
"fn": fn,
|
| 318 |
+
"precision": precision,
|
| 319 |
+
"recall": recall,
|
| 320 |
+
"steps_used": env._step_count,
|
| 321 |
+
"max_steps": env._max_steps,
|
| 322 |
+
"tool_counts": tool_counts,
|
| 323 |
+
"total_tools": sum(tool_counts.values()) - tool_counts["INSPECT"] - tool_counts["FLAG"], # Only investigation tools
|
| 324 |
+
"actions": actions_taken,
|
| 325 |
+
"elapsed_seconds": elapsed,
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
def get_rule_based_action(obs: FakeGangObservation) -> FakeGangAction:
|
| 330 |
+
"""Simple rule-based policy for baseline."""
|
| 331 |
+
# Priority 1: Inspect suspects
|
| 332 |
+
for s in obs.suspect_ids:
|
| 333 |
+
if s not in obs.inspected_ids:
|
| 334 |
+
return FakeGangAction(action_type=ActionType.INSPECT, account_id=s)
|
| 335 |
+
|
| 336 |
+
# Priority 2: Flag high-risk accounts
|
| 337 |
+
for p in obs.visible_accounts:
|
| 338 |
+
if p.account_id in obs.flagged_ids:
|
| 339 |
+
continue
|
| 340 |
+
if p.hub_legitimacy_score > 0.75:
|
| 341 |
+
continue
|
| 342 |
+
if p.shared_ip_count >= 5 or p.fake_risk_score >= 0.60:
|
| 343 |
+
return FakeGangAction(action_type=ActionType.FLAG, account_id=p.account_id)
|
| 344 |
+
|
| 345 |
+
# Priority 3: Inspect uninspected
|
| 346 |
+
uninspected = [i for i in obs.visible_account_ids if i not in obs.inspected_ids]
|
| 347 |
+
if uninspected and obs.steps_remaining > 3:
|
| 348 |
+
return FakeGangAction(action_type=ActionType.INSPECT, account_id=uninspected[0])
|
| 349 |
+
|
| 350 |
+
return FakeGangAction(action_type=ActionType.SUBMIT)
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
# ============================================================================
|
| 354 |
+
# Training Loop
|
| 355 |
+
# ============================================================================
|
| 356 |
+
|
| 357 |
+
def run_training(
|
| 358 |
+
episodes: int,
|
| 359 |
+
task: str,
|
| 360 |
+
backend: str,
|
| 361 |
+
model_id: str,
|
| 362 |
+
output_file: Optional[str],
|
| 363 |
+
verbose: bool,
|
| 364 |
+
) -> List[Dict]:
|
| 365 |
+
"""Run training loop and collect metrics."""
|
| 366 |
+
|
| 367 |
+
env = FakeGangEnvironment()
|
| 368 |
+
results = []
|
| 369 |
+
|
| 370 |
+
print(f"Starting training: {episodes} episodes, task={task}, backend={backend}, model={model_id}")
|
| 371 |
+
print("=" * 80)
|
| 372 |
+
|
| 373 |
+
for ep in range(episodes):
|
| 374 |
+
try:
|
| 375 |
+
result = run_episode(env, task, seed=ep, backend=backend, model_id=model_id, verbose=verbose)
|
| 376 |
+
results.append(result)
|
| 377 |
+
|
| 378 |
+
# Print summary
|
| 379 |
+
print(f"Episode {ep:3d} ({result['platform']:9s}): "
|
| 380 |
+
f"reward={result['reward']:+.3f} | "
|
| 381 |
+
f"grader={result['grader_score']:.3f} | "
|
| 382 |
+
f"TP={result['tp']:2d} FP={result['fp']:2d} FN={result['fn']:2d} | "
|
| 383 |
+
f"P={result['precision']:.2f} R={result['recall']:.2f} | "
|
| 384 |
+
f"tools={result['total_tools']} | "
|
| 385 |
+
f"{result['elapsed_seconds']:.1f}s")
|
| 386 |
+
|
| 387 |
+
except Exception as e:
|
| 388 |
+
print(f"Episode {ep:3d} FAILED: {e}")
|
| 389 |
+
continue
|
| 390 |
+
|
| 391 |
+
print("=" * 80)
|
| 392 |
+
|
| 393 |
+
# Aggregate metrics
|
| 394 |
+
instagram_results = [r for r in results if r["platform"] == "Instagram"]
|
| 395 |
+
snapchat_results = [r for r in results if r["platform"] == "Snapchat"]
|
| 396 |
+
|
| 397 |
+
def avg(lst, key):
|
| 398 |
+
vals = [r[key] for r in lst]
|
| 399 |
+
return sum(vals) / len(vals) if vals else 0.0
|
| 400 |
+
|
| 401 |
+
print(f"\n=== Training Summary ===")
|
| 402 |
+
print(f"Total Episodes: {len(results)}/{episodes}")
|
| 403 |
+
print(f"\nInstagram ({len(instagram_results)} episodes):")
|
| 404 |
+
print(f" Avg Reward: {avg(instagram_results, 'reward'):+.3f}")
|
| 405 |
+
print(f" Avg Grader: {avg(instagram_results, 'grader_score'):.3f}")
|
| 406 |
+
print(f" Avg Precision: {avg(instagram_results, 'precision'):.3f}")
|
| 407 |
+
print(f" Avg Recall: {avg(instagram_results, 'recall'):.3f}")
|
| 408 |
+
print(f" Win Rate: {sum(1 for r in instagram_results if r['grader_score'] >= 0.815) / len(instagram_results) * 100:.1f}%")
|
| 409 |
+
print(f" Avg Tools: {avg(instagram_results, 'total_tools'):.1f}")
|
| 410 |
+
|
| 411 |
+
print(f"\nSnapchat ({len(snapchat_results)} episodes):")
|
| 412 |
+
print(f" Avg Reward: {avg(snapchat_results, 'reward'):+.3f}")
|
| 413 |
+
print(f" Avg Grader: {avg(snapchat_results, 'grader_score'):.3f}")
|
| 414 |
+
print(f" Avg Precision: {avg(snapchat_results, 'precision'):.3f}")
|
| 415 |
+
print(f" Avg Recall: {avg(snapchat_results, 'recall'):.3f}")
|
| 416 |
+
print(f" Win Rate: {sum(1 for r in snapchat_results if r['grader_score'] >= 0.815) / len(snapchat_results) * 100:.1f}%")
|
| 417 |
+
print(f" Avg Tools: {avg(snapchat_results, 'total_tools'):.1f}")
|
| 418 |
+
|
| 419 |
+
# Tool usage breakdown
|
| 420 |
+
all_tools = {}
|
| 421 |
+
for r in results:
|
| 422 |
+
for tool, count in r["tool_counts"].items():
|
| 423 |
+
all_tools[tool] = all_tools.get(tool, 0) + count
|
| 424 |
+
|
| 425 |
+
print(f"\nTool Usage (total):")
|
| 426 |
+
for tool, count in sorted(all_tools.items(), key=lambda x: -x[1]):
|
| 427 |
+
print(f" {tool:25s}: {count:4d} calls")
|
| 428 |
+
|
| 429 |
+
# Save results
|
| 430 |
+
if output_file:
|
| 431 |
+
output_path = Path(output_file)
|
| 432 |
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 433 |
+
|
| 434 |
+
output_data = {
|
| 435 |
+
"metadata": {
|
| 436 |
+
"episodes": episodes,
|
| 437 |
+
"task": task,
|
| 438 |
+
"backend": backend,
|
| 439 |
+
"model_id": model_id,
|
| 440 |
+
"timestamp": datetime.now().isoformat(),
|
| 441 |
+
},
|
| 442 |
+
"results": results,
|
| 443 |
+
"summary": {
|
| 444 |
+
"instagram": {
|
| 445 |
+
"count": len(instagram_results),
|
| 446 |
+
"avg_reward": avg(instagram_results, "reward"),
|
| 447 |
+
"avg_grader": avg(instagram_results, "grader_score"),
|
| 448 |
+
"avg_precision": avg(instagram_results, "precision"),
|
| 449 |
+
"avg_recall": avg(instagram_results, "recall"),
|
| 450 |
+
"win_rate": sum(1 for r in instagram_results if r["grader_score"] >= 0.815) / len(instagram_results) if instagram_results else 0,
|
| 451 |
+
"avg_tools": avg(instagram_results, "total_tools"),
|
| 452 |
+
},
|
| 453 |
+
"snapchat": {
|
| 454 |
+
"count": len(snapchat_results),
|
| 455 |
+
"avg_reward": avg(snapchat_results, "reward"),
|
| 456 |
+
"avg_grader": avg(snapchat_results, "grader_score"),
|
| 457 |
+
"avg_precision": avg(snapchat_results, "precision"),
|
| 458 |
+
"avg_recall": avg(snapchat_results, "recall"),
|
| 459 |
+
"win_rate": sum(1 for r in snapchat_results if r["grader_score"] >= 0.815) / len(snapchat_results) if snapchat_results else 0,
|
| 460 |
+
"avg_tools": avg(snapchat_results, "total_tools"),
|
| 461 |
+
},
|
| 462 |
+
"tool_usage": all_tools,
|
| 463 |
+
},
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
output_path.write_text(json.dumps(output_data, indent=2))
|
| 467 |
+
print(f"\n✓ Results saved to {output_file}")
|
| 468 |
+
|
| 469 |
+
return results
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
# ============================================================================
|
| 473 |
+
# CLI
|
| 474 |
+
# ============================================================================
|
| 475 |
+
|
| 476 |
+
def main():
|
| 477 |
+
parser = argparse.ArgumentParser(description="Train Fake Gang Detection agent")
|
| 478 |
+
|
| 479 |
+
# Training params
|
| 480 |
+
parser.add_argument("--episodes", type=int, default=50, help="Number of episodes")
|
| 481 |
+
parser.add_argument("--task", choices=["easy", "medium", "hard"], default="easy", help="Task difficulty")
|
| 482 |
+
|
| 483 |
+
# Model selection
|
| 484 |
+
parser.add_argument("--backend", choices=["bedrock", "hf", "rule"], default="rule",
|
| 485 |
+
help="LLM backend (bedrock=AWS, hf=HuggingFace, rule=baseline)")
|
| 486 |
+
parser.add_argument("--model-id", default="qwen.qwen3-next-80b-a3b",
|
| 487 |
+
help="Model ID (Bedrock: qwen.qwen3-next-80b-a3b, HF: Qwen/Qwen2.5-72B-Instruct)")
|
| 488 |
+
|
| 489 |
+
# Output
|
| 490 |
+
parser.add_argument("--output", "-o", default="results/training_results.json",
|
| 491 |
+
help="Output JSON file for metrics")
|
| 492 |
+
parser.add_argument("--verbose", "-v", action="store_true", help="Show detailed action log")
|
| 493 |
+
|
| 494 |
+
args = parser.parse_args()
|
| 495 |
+
|
| 496 |
+
# Run training
|
| 497 |
+
run_training(
|
| 498 |
+
episodes=args.episodes,
|
| 499 |
+
task=args.task,
|
| 500 |
+
backend=args.backend,
|
| 501 |
+
model_id=args.model_id,
|
| 502 |
+
output_file=args.output,
|
| 503 |
+
verbose=args.verbose,
|
| 504 |
+
)
|
| 505 |
+
|
| 506 |
+
|
| 507 |
+
if __name__ == "__main__":
|
| 508 |
+
main()
|
assets/episode.png
ADDED
|
assets/formulas-1.png
ADDED
|
Git LFS Details
|
assets/formulas-2.png
ADDED
|
assets/gs.png
ADDED
|
assets/hybrid.png
ADDED
|
Git LFS Details
|
assets/logo.png
ADDED
|
assets/reflexion.png
ADDED
|
Git LFS Details
|
assets/sys arch.png
ADDED
|
Git LFS Details
|
baseline_agent.py
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Baseline agents for all three tasks of the Fake Gang Detection environment.
|
| 2 |
+
|
| 3 |
+
Run directly:
|
| 4 |
+
python baseline_agent.py --task easy --episodes 10
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import argparse
|
| 10 |
+
import sys
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from typing import List, Optional
|
| 13 |
+
|
| 14 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
| 15 |
+
|
| 16 |
+
from client import FakeGangEnvClient, StepResult
|
| 17 |
+
from models import AccountProfile, FakeGangObservation, ActionType
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# ---------------------------------------------------------------------------
|
| 21 |
+
# Scoring helper
|
| 22 |
+
# ---------------------------------------------------------------------------
|
| 23 |
+
|
| 24 |
+
def _gang_score(profile: AccountProfile, task: str) -> float:
|
| 25 |
+
"""Simple composite fake-likelihood score (0–1)."""
|
| 26 |
+
# Normalise account age: newer = more suspicious (fakes created recently)
|
| 27 |
+
age_score = max(0.0, 1.0 - profile.account_age_days / 500.0)
|
| 28 |
+
# Posting hour clustering: peak hour 14 is suspicious
|
| 29 |
+
hour_score = max(0.0, 1.0 - abs(profile.avg_post_hour - 14.0) / 12.0)
|
| 30 |
+
return (
|
| 31 |
+
profile.photo_reuse_score * 0.40
|
| 32 |
+
+ profile.bio_template_score * 0.30
|
| 33 |
+
+ age_score * 0.20
|
| 34 |
+
+ hour_score * 0.10
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
# ---------------------------------------------------------------------------
|
| 39 |
+
# Easy baseline: signal scanner
|
| 40 |
+
# ---------------------------------------------------------------------------
|
| 41 |
+
|
| 42 |
+
def easy_agent(env: FakeGangEnvClient, seed: int = 0) -> StepResult:
|
| 43 |
+
result = env.reset(task="easy", seed=seed)
|
| 44 |
+
obs = result.observation
|
| 45 |
+
|
| 46 |
+
PHOTO_THRESH = 0.50
|
| 47 |
+
BIO_THRESH = 0.40
|
| 48 |
+
|
| 49 |
+
while not obs.done:
|
| 50 |
+
# Pick an uninspected account to inspect
|
| 51 |
+
uninspected = [
|
| 52 |
+
i for i in obs.visible_account_ids if i not in obs.inspected_ids
|
| 53 |
+
]
|
| 54 |
+
|
| 55 |
+
if uninspected:
|
| 56 |
+
target = uninspected[0]
|
| 57 |
+
result = env.inspect(target)
|
| 58 |
+
obs = result.observation
|
| 59 |
+
|
| 60 |
+
# Flag if it matches both signal thresholds
|
| 61 |
+
profile = next((p for p in obs.visible_accounts if p.account_id == target), None)
|
| 62 |
+
if profile and (
|
| 63 |
+
profile.photo_reuse_score > PHOTO_THRESH
|
| 64 |
+
and profile.bio_template_score > BIO_THRESH
|
| 65 |
+
):
|
| 66 |
+
result = env.flag(target)
|
| 67 |
+
obs = result.observation
|
| 68 |
+
|
| 69 |
+
# Explore its network if we don't have enough flags yet
|
| 70 |
+
if len(obs.flagged_ids) < 10:
|
| 71 |
+
result = env.investigate_network(target)
|
| 72 |
+
obs = result.observation
|
| 73 |
+
else:
|
| 74 |
+
# Lower thresholds if we're running low on steps
|
| 75 |
+
if obs.steps_remaining < 10:
|
| 76 |
+
PHOTO_THRESH = max(0.20, PHOTO_THRESH - 0.10)
|
| 77 |
+
BIO_THRESH = max(0.20, BIO_THRESH - 0.10)
|
| 78 |
+
# Re-evaluate all inspected accounts with looser thresholds
|
| 79 |
+
for p in obs.visible_accounts:
|
| 80 |
+
if p.account_id not in obs.flagged_ids:
|
| 81 |
+
if (p.photo_reuse_score > PHOTO_THRESH
|
| 82 |
+
and p.bio_template_score > BIO_THRESH):
|
| 83 |
+
result = env.flag(p.account_id)
|
| 84 |
+
obs = result.observation
|
| 85 |
+
|
| 86 |
+
result = env.submit()
|
| 87 |
+
obs = result.observation
|
| 88 |
+
|
| 89 |
+
if obs.steps_remaining <= 0 or obs.done:
|
| 90 |
+
if not obs.done:
|
| 91 |
+
result = env.submit()
|
| 92 |
+
obs = result.observation
|
| 93 |
+
break
|
| 94 |
+
|
| 95 |
+
return result
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
# ---------------------------------------------------------------------------
|
| 99 |
+
# Medium baseline: time-aware scanner
|
| 100 |
+
# ---------------------------------------------------------------------------
|
| 101 |
+
|
| 102 |
+
def medium_agent(env: FakeGangEnvClient, seed: int = 0) -> StepResult:
|
| 103 |
+
result = env.reset(task="medium", seed=seed)
|
| 104 |
+
obs = result.observation
|
| 105 |
+
|
| 106 |
+
max_steps = 50
|
| 107 |
+
evasion_step = 20
|
| 108 |
+
|
| 109 |
+
while not obs.done:
|
| 110 |
+
steps_used = max_steps - obs.steps_remaining
|
| 111 |
+
|
| 112 |
+
uninspected = [i for i in obs.visible_account_ids if i not in obs.inspected_ids]
|
| 113 |
+
|
| 114 |
+
# Phase 1: race against evasion — use graph traversal
|
| 115 |
+
if steps_used < evasion_step - 5 and uninspected:
|
| 116 |
+
target = uninspected[0]
|
| 117 |
+
result = env.inspect(target)
|
| 118 |
+
obs = result.observation
|
| 119 |
+
profile = next((p for p in obs.visible_accounts if p.account_id == target), None)
|
| 120 |
+
if profile and profile.photo_reuse_score > 0.35:
|
| 121 |
+
result = env.flag(target)
|
| 122 |
+
obs = result.observation
|
| 123 |
+
result = env.investigate_network(target)
|
| 124 |
+
obs = result.observation
|
| 125 |
+
|
| 126 |
+
# Phase 2: after evasion — rely on features only
|
| 127 |
+
elif uninspected:
|
| 128 |
+
target = uninspected[0]
|
| 129 |
+
result = env.inspect(target)
|
| 130 |
+
obs = result.observation
|
| 131 |
+
profile = next((p for p in obs.visible_accounts if p.account_id == target), None)
|
| 132 |
+
if profile:
|
| 133 |
+
score = _gang_score(profile, "medium")
|
| 134 |
+
if score > 0.45:
|
| 135 |
+
result = env.flag(target)
|
| 136 |
+
obs = result.observation
|
| 137 |
+
|
| 138 |
+
else:
|
| 139 |
+
# No more to inspect — submit
|
| 140 |
+
result = env.submit()
|
| 141 |
+
obs = result.observation
|
| 142 |
+
break
|
| 143 |
+
|
| 144 |
+
if obs.steps_remaining <= 2 or obs.done:
|
| 145 |
+
if not obs.done:
|
| 146 |
+
result = env.submit()
|
| 147 |
+
obs = result.observation
|
| 148 |
+
break
|
| 149 |
+
|
| 150 |
+
return result
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
# ---------------------------------------------------------------------------
|
| 154 |
+
# Hard baseline: feature-only detective
|
| 155 |
+
# ---------------------------------------------------------------------------
|
| 156 |
+
|
| 157 |
+
def hard_agent(env: FakeGangEnvClient, seed: int = 0) -> StepResult:
|
| 158 |
+
result = env.reset(task="hard", seed=seed)
|
| 159 |
+
obs = result.observation
|
| 160 |
+
|
| 161 |
+
max_steps = 80
|
| 162 |
+
submit_by_step = 60 # submit before too many evasion events
|
| 163 |
+
|
| 164 |
+
while not obs.done:
|
| 165 |
+
steps_used = max_steps - obs.steps_remaining
|
| 166 |
+
uninspected = [i for i in obs.visible_account_ids if i not in obs.inspected_ids]
|
| 167 |
+
|
| 168 |
+
# Inspect until we have a dataset or time's up
|
| 169 |
+
if uninspected and steps_used < submit_by_step:
|
| 170 |
+
target = uninspected[0]
|
| 171 |
+
result = env.inspect(target)
|
| 172 |
+
obs = result.observation
|
| 173 |
+
|
| 174 |
+
# Discover more IDs via network expansion (spend the extra step)
|
| 175 |
+
if len(obs.visible_account_ids) < 100 and obs.steps_remaining > 15:
|
| 176 |
+
result = env.investigate_network(target)
|
| 177 |
+
obs = result.observation
|
| 178 |
+
|
| 179 |
+
else:
|
| 180 |
+
# Score all inspected accounts and flag top-12
|
| 181 |
+
scored = sorted(
|
| 182 |
+
obs.visible_accounts,
|
| 183 |
+
key=lambda p: _gang_score(p, "hard") + 0.3 * p.name_change_count,
|
| 184 |
+
reverse=True,
|
| 185 |
+
)
|
| 186 |
+
# Unflag everything first
|
| 187 |
+
for fid in list(obs.flagged_ids):
|
| 188 |
+
result = env.unflag(fid)
|
| 189 |
+
obs = result.observation
|
| 190 |
+
|
| 191 |
+
# Flag top 12 (a bit generous to improve recall)
|
| 192 |
+
for profile in scored[:12]:
|
| 193 |
+
result = env.flag(profile.account_id)
|
| 194 |
+
obs = result.observation
|
| 195 |
+
|
| 196 |
+
result = env.submit()
|
| 197 |
+
obs = result.observation
|
| 198 |
+
break
|
| 199 |
+
|
| 200 |
+
if obs.steps_remaining <= 2 or obs.done:
|
| 201 |
+
if not obs.done:
|
| 202 |
+
result = env.submit()
|
| 203 |
+
obs = result.observation
|
| 204 |
+
break
|
| 205 |
+
|
| 206 |
+
return result
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
# ---------------------------------------------------------------------------
|
| 210 |
+
# Evaluation runner
|
| 211 |
+
# ---------------------------------------------------------------------------
|
| 212 |
+
|
| 213 |
+
AGENTS = {
|
| 214 |
+
"easy": easy_agent,
|
| 215 |
+
"medium": medium_agent,
|
| 216 |
+
"hard": hard_agent,
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def evaluate(task: str, episodes: int = 10, base_url: str = "http://localhost:8000") -> None:
|
| 221 |
+
agent_fn = AGENTS[task]
|
| 222 |
+
wins = 0
|
| 223 |
+
total_reward = 0.0
|
| 224 |
+
|
| 225 |
+
with FakeGangEnvClient(base_url=base_url) as env:
|
| 226 |
+
for seed in range(episodes):
|
| 227 |
+
result = agent_fn(env, seed=seed)
|
| 228 |
+
msg = result.message
|
| 229 |
+
reward = result.reward or 0.0
|
| 230 |
+
total_reward += reward
|
| 231 |
+
won = "[WIN]" in msg
|
| 232 |
+
if won:
|
| 233 |
+
wins += 1
|
| 234 |
+
print(f"Episode {seed:3d} | {'WIN ' if won else 'LOSS'} | reward={reward:+.2f} | {msg}")
|
| 235 |
+
|
| 236 |
+
print(f"\n{task.upper()} — wins: {wins}/{episodes} ({100*wins/episodes:.0f}%) | avg reward: {total_reward/episodes:.2f}")
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
# ---------------------------------------------------------------------------
|
| 240 |
+
# CLI
|
| 241 |
+
# ---------------------------------------------------------------------------
|
| 242 |
+
|
| 243 |
+
if __name__ == "__main__":
|
| 244 |
+
parser = argparse.ArgumentParser(description="Run baseline agent against the Fake Gang env.")
|
| 245 |
+
parser.add_argument("--task", choices=["easy", "medium", "hard"], default="easy")
|
| 246 |
+
parser.add_argument("--episodes", type=int, default=10)
|
| 247 |
+
parser.add_argument("--url", default="http://localhost:8000")
|
| 248 |
+
args = parser.parse_args()
|
| 249 |
+
evaluate(args.task, args.episodes, args.url)
|
bedrock_model.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import boto3
|
| 2 |
+
import json
|
| 3 |
+
import os
|
| 4 |
+
from botocore.exceptions import ClientError
|
| 5 |
+
from typing import Any, List, Dict, Optional, Union
|
| 6 |
+
|
| 7 |
+
# --- Credentials setup ---
|
| 8 |
+
AWS_ACCESS_KEY = os.environ.get("AWS_ACCESS_KEY_ID")
|
| 9 |
+
AWS_SECRET_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY")
|
| 10 |
+
|
| 11 |
+
# Bedrock Marketplace model endpoint ARN (replace with your own)
|
| 12 |
+
MODEL_ID = "qwen.qwen3-next-80b-a3b"
|
| 13 |
+
|
| 14 |
+
# Build the Bedrock runtime client
|
| 15 |
+
client = boto3.client(
|
| 16 |
+
service_name="bedrock-runtime",
|
| 17 |
+
region_name="us-east-1",
|
| 18 |
+
aws_access_key_id=AWS_ACCESS_KEY,
|
| 19 |
+
aws_secret_access_key=AWS_SECRET_KEY,
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def invoke_qwen(
|
| 24 |
+
prompt: str,
|
| 25 |
+
system: str = None,
|
| 26 |
+
max_tokens: int = 1024,
|
| 27 |
+
temperature: float = 0.3,
|
| 28 |
+
images: Optional[List[Dict[str, Union[str, bytes]]]] = None
|
| 29 |
+
) -> str:
|
| 30 |
+
"""
|
| 31 |
+
Invoke the Qwen VL model via Bedrock Converse API with optional image input.
|
| 32 |
+
|
| 33 |
+
Args:
|
| 34 |
+
prompt: Text prompt for the model.
|
| 35 |
+
system: Optional system prompt.
|
| 36 |
+
max_tokens: Maximum tokens to generate.
|
| 37 |
+
images: Optional list of image dictionaries. Each dict must contain:
|
| 38 |
+
- "bytes": raw image bytes (e.g., from open(file, "rb").read())
|
| 39 |
+
OR
|
| 40 |
+
- "path": local file path to an image (will be read as raw bytes).
|
| 41 |
+
- "format": image format string ("jpeg", "png", "gif", or "webp").
|
| 42 |
+
|
| 43 |
+
Returns:
|
| 44 |
+
Generated text response.
|
| 45 |
+
"""
|
| 46 |
+
# Start building the content blocks for the user message
|
| 47 |
+
content_blocks: List[Dict] = [{"text": prompt}]
|
| 48 |
+
|
| 49 |
+
# Add image blocks if any are supplied
|
| 50 |
+
if images:
|
| 51 |
+
for img in images:
|
| 52 |
+
# Determine raw bytes from either "bytes" or "path"
|
| 53 |
+
if "bytes" in img:
|
| 54 |
+
img_bytes = img["bytes"]
|
| 55 |
+
elif "path" in img:
|
| 56 |
+
with open(img["path"], "rb") as f:
|
| 57 |
+
img_bytes = f.read()
|
| 58 |
+
else:
|
| 59 |
+
raise ValueError(
|
| 60 |
+
"Each image dict must include either 'bytes' (raw bytes) "
|
| 61 |
+
"or 'path' (file path) plus a 'format' key."
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
if "format" not in img:
|
| 65 |
+
raise ValueError("Each image dict must include a 'format' (e.g., 'jpeg').")
|
| 66 |
+
|
| 67 |
+
# Build the image block as required by the Converse API
|
| 68 |
+
image_block = {
|
| 69 |
+
"image": {
|
| 70 |
+
"format": img["format"],
|
| 71 |
+
"source": {"bytes": img_bytes} # raw bytes, NOT base64‑encoded
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
content_blocks.append(image_block)
|
| 75 |
+
|
| 76 |
+
# Assemble the final message
|
| 77 |
+
messages = [{"role": "user", "content": content_blocks}]
|
| 78 |
+
|
| 79 |
+
# Prepare the Converse API call
|
| 80 |
+
kwargs = {
|
| 81 |
+
"modelId": MODEL_ID, # Marketplace endpoint ARN
|
| 82 |
+
"messages": messages,
|
| 83 |
+
"inferenceConfig": {
|
| 84 |
+
"maxTokens": max_tokens,
|
| 85 |
+
"temperature": temperature,
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
if system:
|
| 89 |
+
kwargs["system"] = [{"text": system}]
|
| 90 |
+
|
| 91 |
+
try:
|
| 92 |
+
response = client.converse(**kwargs)
|
| 93 |
+
output = response["output"]["message"]["content"][0]["text"]
|
| 94 |
+
usage = response["usage"]
|
| 95 |
+
print(f"[Tokens] in={usage['inputTokens']} out={usage['outputTokens']}")
|
| 96 |
+
return output
|
| 97 |
+
except ClientError as e:
|
| 98 |
+
raise RuntimeError(f"Bedrock error: {e.response['Error']['Message']}")
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def _parse_score_response(text: str) -> Dict[str, Any]:
|
| 102 |
+
"""Parse JSON object with score, issues, summary from model output."""
|
| 103 |
+
raw = (text or "").strip()
|
| 104 |
+
start, end = raw.find("{"), raw.rfind("}")
|
| 105 |
+
if start >= 0 and end > start:
|
| 106 |
+
raw = raw[start : end + 1]
|
| 107 |
+
try:
|
| 108 |
+
d = json.loads(raw)
|
| 109 |
+
except json.JSONDecodeError:
|
| 110 |
+
return {"score": 0, "issues": ["Could not parse model JSON"], "summary": text[:400] if text else ""}
|
| 111 |
+
score = d.get("score", 0)
|
| 112 |
+
try:
|
| 113 |
+
score_i = int(score)
|
| 114 |
+
except (TypeError, ValueError):
|
| 115 |
+
score_i = 0
|
| 116 |
+
score_i = max(0, min(100, score_i))
|
| 117 |
+
issues = d.get("issues")
|
| 118 |
+
if issues is None:
|
| 119 |
+
issues = []
|
| 120 |
+
if isinstance(issues, str):
|
| 121 |
+
issues = [issues]
|
| 122 |
+
if not isinstance(issues, list):
|
| 123 |
+
issues = []
|
| 124 |
+
issues = [str(x).strip() for x in issues if str(x).strip()]
|
| 125 |
+
summary = str(d.get("summary", "") or "").strip()
|
| 126 |
+
return {"score": score_i, "issues": issues[:12], "summary": summary}
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def score_design_against_spec(image_bytes: bytes, spec: Optional[str]) -> Dict[str, Any]:
|
| 130 |
+
"""
|
| 131 |
+
Score a UI screenshot (PNG bytes) against the product spec via the vision model.
|
| 132 |
+
Returns dict: score (0-100), issues (list of str), summary (str).
|
| 133 |
+
"""
|
| 134 |
+
system = (
|
| 135 |
+
"You are a product design QA assistant. Compare the screenshot to the product spec. "
|
| 136 |
+
"Return ONLY a single JSON object, no markdown, with keys: "
|
| 137 |
+
'score (integer 0-100), issues (array of short strings, max 8 items), '
|
| 138 |
+
'summary (one sentence). Be strict about spec mismatches.'
|
| 139 |
+
)
|
| 140 |
+
spec_block = (spec or "").strip() or (
|
| 141 |
+
"(No spec was provided — score clarity, hierarchy, visual polish, and common UX patterns.)"
|
| 142 |
+
)
|
| 143 |
+
prompt = (
|
| 144 |
+
"Evaluate this design for the following product spec.\n\n"
|
| 145 |
+
f"SPEC:\n{spec_block}\n\n"
|
| 146 |
+
"Output JSON only."
|
| 147 |
+
)
|
| 148 |
+
text = invoke_qwen(
|
| 149 |
+
prompt=prompt,
|
| 150 |
+
system=system,
|
| 151 |
+
max_tokens=1024,
|
| 152 |
+
temperature=0.2,
|
| 153 |
+
images=[{"bytes": image_bytes, "format": "png"}],
|
| 154 |
+
)
|
| 155 |
+
return _parse_score_response(text)
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
# --- Example usage ---
|
| 159 |
+
if __name__ == "__main__":
|
| 160 |
+
# 1️⃣ Text‑only call (same as before)
|
| 161 |
+
text_result = invoke_qwen(
|
| 162 |
+
prompt="Hello",
|
| 163 |
+
# system="You are a content extractor. Return content as JSON list.",
|
| 164 |
+
)
|
| 165 |
+
print("\nText‑only result:")
|
| 166 |
+
print(text_result)
|
| 167 |
+
|
check.sh
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# GraphStrike Round 2 — Full System Check
|
| 3 |
+
# Run: bash check_round2.sh
|
| 4 |
+
# Assumes server is running at localhost:7860
|
| 5 |
+
|
| 6 |
+
BASE="http://localhost:7860"
|
| 7 |
+
echo "========================================"
|
| 8 |
+
echo "GRAPHSTRIKE ROUND 2 — SYSTEM CHECK"
|
| 9 |
+
echo "========================================"
|
| 10 |
+
|
| 11 |
+
# -------------------------------------------------------
|
| 12 |
+
# CHECK 1 — Server is alive
|
| 13 |
+
# -------------------------------------------------------
|
| 14 |
+
echo ""
|
| 15 |
+
echo "CHECK 1: Server health"
|
| 16 |
+
curl -s "$BASE/" | python3 -m json.tool 2>/dev/null || \
|
| 17 |
+
curl -s "$BASE/health" | python3 -m json.tool 2>/dev/null || \
|
| 18 |
+
echo " (no health endpoint — checking /tasks instead)"
|
| 19 |
+
|
| 20 |
+
# -------------------------------------------------------
|
| 21 |
+
# CHECK 2 — Tasks endpoint shows Round 2 actions
|
| 22 |
+
# -------------------------------------------------------
|
| 23 |
+
echo ""
|
| 24 |
+
echo "CHECK 2: /tasks — must show all Round 2 action types"
|
| 25 |
+
curl -s "$BASE/tasks" | python3 -m json.tool
|
| 26 |
+
|
| 27 |
+
# -------------------------------------------------------
|
| 28 |
+
# CHECK 3 — Reset episode (instagram)
|
| 29 |
+
# -------------------------------------------------------
|
| 30 |
+
echo ""
|
| 31 |
+
echo "CHECK 3: /reset — instagram, easy task"
|
| 32 |
+
RESET=$(curl -s -X POST "$BASE/reset" \
|
| 33 |
+
-H "Content-Type: application/json" \
|
| 34 |
+
-d '{"task": "easy"}')
|
| 35 |
+
echo $RESET | python3 -m json.tool
|
| 36 |
+
|
| 37 |
+
# Extract session or episode info if present
|
| 38 |
+
EPISODE_ID=$(echo $RESET | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('episode_id',''))" 2>/dev/null)
|
| 39 |
+
echo " Episode ID: $EPISODE_ID"
|
| 40 |
+
|
| 41 |
+
# -------------------------------------------------------
|
| 42 |
+
# CHECK 4 — GET_POLICY (lowercase — correct form)
|
| 43 |
+
# -------------------------------------------------------
|
| 44 |
+
echo ""
|
| 45 |
+
echo "CHECK 4: step GET_POLICY (lowercase) — must return threshold"
|
| 46 |
+
curl -s -X POST "$BASE/step" \
|
| 47 |
+
-H "Content-Type: application/json" \
|
| 48 |
+
-d '{"action_type": "get_policy"}' | python3 -m json.tool
|
| 49 |
+
|
| 50 |
+
# Extract a real account_id from the reset observation BEFORE the first inspect.
|
| 51 |
+
ACCOUNT_ID=$(echo $RESET | python3 -c "
|
| 52 |
+
import sys, json
|
| 53 |
+
d = json.load(sys.stdin)
|
| 54 |
+
obs = d.get('observation', d)
|
| 55 |
+
accounts = obs.get('visible_account_ids') or obs.get('visible_accounts') or []
|
| 56 |
+
if accounts:
|
| 57 |
+
print(accounts[0] if isinstance(accounts[0], str) else accounts[0].get('account_id','acc_000'))
|
| 58 |
+
else:
|
| 59 |
+
print('acc_000')
|
| 60 |
+
" 2>/dev/null)
|
| 61 |
+
echo " Using account_id: $ACCOUNT_ID"
|
| 62 |
+
|
| 63 |
+
# -------------------------------------------------------
|
| 64 |
+
# CHECK 5 — INSPECT first visible account
|
| 65 |
+
# -------------------------------------------------------
|
| 66 |
+
echo ""
|
| 67 |
+
echo "CHECK 5: step inspect — first visible account ($ACCOUNT_ID)"
|
| 68 |
+
INSPECT=$(curl -s -X POST "$BASE/step" \
|
| 69 |
+
-H "Content-Type: application/json" \
|
| 70 |
+
-d "{\"action_type\": \"inspect\", \"account_id\": \"$ACCOUNT_ID\"}")
|
| 71 |
+
echo $INSPECT | python3 -m json.tool
|
| 72 |
+
|
| 73 |
+
# -------------------------------------------------------
|
| 74 |
+
# CHECK 6 — REVERSE_IMAGE_SEARCH
|
| 75 |
+
# photo_reuse_score must be None before, filled after
|
| 76 |
+
# -------------------------------------------------------
|
| 77 |
+
echo ""
|
| 78 |
+
echo "CHECK 6: reverse_image_search — must populate photo_reuse_score"
|
| 79 |
+
echo " Before call — check photo_reuse_score is hidden:"
|
| 80 |
+
curl -s -X POST "$BASE/step" \
|
| 81 |
+
-H "Content-Type: application/json" \
|
| 82 |
+
-d "{\"action_type\": \"inspect\", \"account_id\": \"$ACCOUNT_ID\"}" | \
|
| 83 |
+
ACCOUNT_ID="$ACCOUNT_ID" python3 -c "
|
| 84 |
+
import sys, json, os
|
| 85 |
+
d = json.load(sys.stdin)
|
| 86 |
+
obs = d.get('observation', {})
|
| 87 |
+
target = os.environ['ACCOUNT_ID']
|
| 88 |
+
profile = next((a for a in obs.get('visible_accounts', []) if a.get('account_id') == target), {})
|
| 89 |
+
val = profile.get('photo_reuse_score', 'KEY_MISSING')
|
| 90 |
+
print(f' photo_reuse_score = {val}')
|
| 91 |
+
print(' PASS — hidden before tool call' if (val in (None, 0.0)) else
|
| 92 |
+
f' WARN — already visible (={val})' if val != 'KEY_MISSING' else
|
| 93 |
+
' WARN — field not found in response')
|
| 94 |
+
"
|
| 95 |
+
|
| 96 |
+
echo " After reverse_image_search:"
|
| 97 |
+
curl -s -X POST "$BASE/step" \
|
| 98 |
+
-H "Content-Type: application/json" \
|
| 99 |
+
-d "{\"action_type\": \"reverse_image_search\", \"account_id\": \"$ACCOUNT_ID\"}" | \
|
| 100 |
+
ACCOUNT_ID="$ACCOUNT_ID" python3 -c "
|
| 101 |
+
import sys, json, os
|
| 102 |
+
d = json.load(sys.stdin)
|
| 103 |
+
obs = d.get('observation', {})
|
| 104 |
+
target = os.environ['ACCOUNT_ID']
|
| 105 |
+
profile = next((a for a in obs.get('visible_accounts', []) if a.get('account_id') == target), {})
|
| 106 |
+
val = profile.get('photo_reuse_score', 'KEY_MISSING')
|
| 107 |
+
reward = d.get('reward', 'N/A')
|
| 108 |
+
print(f' photo_reuse_score = {val}')
|
| 109 |
+
print(f' reward = {reward}')
|
| 110 |
+
print(' PASS — signal revealed' if val not in (None, 'KEY_MISSING') else
|
| 111 |
+
' FAIL — signal still hidden after tool call')
|
| 112 |
+
"
|
| 113 |
+
|
| 114 |
+
# -------------------------------------------------------
|
| 115 |
+
# CHECK 7 — ANALYZE_BIO
|
| 116 |
+
# -------------------------------------------------------
|
| 117 |
+
echo ""
|
| 118 |
+
echo "CHECK 7: analyze_bio — must populate bio_template_score"
|
| 119 |
+
curl -s -X POST "$BASE/step" \
|
| 120 |
+
-H "Content-Type: application/json" \
|
| 121 |
+
-d "{\"action_type\": \"analyze_bio\", \"account_id\": \"$ACCOUNT_ID\"}" | \
|
| 122 |
+
ACCOUNT_ID="$ACCOUNT_ID" python3 -c "
|
| 123 |
+
import sys, json, os
|
| 124 |
+
d = json.load(sys.stdin)
|
| 125 |
+
obs = d.get('observation', {})
|
| 126 |
+
target = os.environ['ACCOUNT_ID']
|
| 127 |
+
profile = next((a for a in obs.get('visible_accounts', []) if a.get('account_id') == target), {})
|
| 128 |
+
val = profile.get('bio_template_score', 'KEY_MISSING')
|
| 129 |
+
reward = d.get('reward', 'N/A')
|
| 130 |
+
print(f' bio_template_score = {val}')
|
| 131 |
+
print(f' reward = {reward}')
|
| 132 |
+
print(' PASS' if val not in (None, 0.0, 'KEY_MISSING') else ' FAIL')
|
| 133 |
+
"
|
| 134 |
+
|
| 135 |
+
# -------------------------------------------------------
|
| 136 |
+
# CHECK 8 — CHECK_IP
|
| 137 |
+
# -------------------------------------------------------
|
| 138 |
+
echo ""
|
| 139 |
+
echo "CHECK 8: check_ip — must reveal ip_cluster, costs 2 steps"
|
| 140 |
+
curl -s -X POST "$BASE/step" \
|
| 141 |
+
-H "Content-Type: application/json" \
|
| 142 |
+
-d "{\"action_type\": \"check_ip\", \"account_id\": \"$ACCOUNT_ID\"}" | \
|
| 143 |
+
ACCOUNT_ID="$ACCOUNT_ID" python3 -c "
|
| 144 |
+
import sys, json, os
|
| 145 |
+
d = json.load(sys.stdin)
|
| 146 |
+
obs = d.get('observation', {})
|
| 147 |
+
target = os.environ['ACCOUNT_ID']
|
| 148 |
+
profile = next((a for a in obs.get('visible_accounts', []) if a.get('account_id') == target), {})
|
| 149 |
+
# server exposes the cluster id via the message and shared_ip_count via the profile.
|
| 150 |
+
shared = profile.get('shared_ip_count', 'KEY_MISSING')
|
| 151 |
+
msg = d.get('message','')
|
| 152 |
+
reward = d.get('reward', 'N/A')
|
| 153 |
+
print(f' shared_ip_count = {shared}')
|
| 154 |
+
print(f' message excerpt: {msg[:120]}')
|
| 155 |
+
print(f' reward = {reward}')
|
| 156 |
+
print(' PASS' if 'cluster' in msg.lower() or shared not in (None,'KEY_MISSING') else ' FAIL')
|
| 157 |
+
"
|
| 158 |
+
|
| 159 |
+
# -------------------------------------------------------
|
| 160 |
+
# CHECK 9 — GET_POLICY first-step bonus
|
| 161 |
+
# -------------------------------------------------------
|
| 162 |
+
echo ""
|
| 163 |
+
echo "CHECK 9: GET_POLICY at step 0 must give +0.20 reward"
|
| 164 |
+
echo " Resetting fresh episode..."
|
| 165 |
+
curl -s -X POST "$BASE/reset" \
|
| 166 |
+
-H "Content-Type: application/json" \
|
| 167 |
+
-d '{"task": "easy"}' > /dev/null
|
| 168 |
+
|
| 169 |
+
curl -s -X POST "$BASE/step" \
|
| 170 |
+
-H "Content-Type: application/json" \
|
| 171 |
+
-d '{"action_type": "get_policy"}' | \
|
| 172 |
+
python3 -c "
|
| 173 |
+
import sys, json
|
| 174 |
+
d = json.load(sys.stdin)
|
| 175 |
+
reward = d.get('reward', None)
|
| 176 |
+
msg = d.get('message', '')
|
| 177 |
+
threshold = None
|
| 178 |
+
try:
|
| 179 |
+
import re
|
| 180 |
+
m = re.search(r'threshold[=:\s]+([\d.]+)', str(d))
|
| 181 |
+
if m: threshold = m.group(1)
|
| 182 |
+
except: pass
|
| 183 |
+
print(f' reward = {reward}')
|
| 184 |
+
print(f' threshold found = {threshold}')
|
| 185 |
+
print(' PASS — +0.20 bonus received' if reward and float(reward) >= 0.15 else
|
| 186 |
+
f' WARN — reward={reward}, expected ~0.20')
|
| 187 |
+
"
|
| 188 |
+
|
| 189 |
+
# -------------------------------------------------------
|
| 190 |
+
# CHECK 10 — Redundant tool call penalty
|
| 191 |
+
# -------------------------------------------------------
|
| 192 |
+
echo ""
|
| 193 |
+
echo "CHECK 10: Calling reverse_image_search twice must give -0.05 penalty"
|
| 194 |
+
curl -s -X POST "$BASE/reset" \
|
| 195 |
+
-H "Content-Type: application/json" \
|
| 196 |
+
-d '{"task": "easy"}' > /dev/null
|
| 197 |
+
|
| 198 |
+
# First call — should give normal reward
|
| 199 |
+
R1=$(curl -s -X POST "$BASE/step" \
|
| 200 |
+
-H "Content-Type: application/json" \
|
| 201 |
+
-d "{\"action_type\": \"reverse_image_search\", \"account_id\": \"$ACCOUNT_ID\"}" | \
|
| 202 |
+
python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('reward',0))")
|
| 203 |
+
|
| 204 |
+
# Second call on same account — should give -0.05
|
| 205 |
+
R2=$(curl -s -X POST "$BASE/step" \
|
| 206 |
+
-H "Content-Type: application/json" \
|
| 207 |
+
-d "{\"action_type\": \"reverse_image_search\", \"account_id\": \"$ACCOUNT_ID\"}" | \
|
| 208 |
+
python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('reward',0))")
|
| 209 |
+
|
| 210 |
+
echo " First call reward: $R1"
|
| 211 |
+
echo " Second call reward: $R2"
|
| 212 |
+
python3 -c "
|
| 213 |
+
r1, r2 = $R1, $R2
|
| 214 |
+
print(' PASS — penalty applied on redundant call' if r2 < r1 else
|
| 215 |
+
'FAIL — no penalty on redundant call (should be -0.05)')
|
| 216 |
+
"
|
| 217 |
+
|
| 218 |
+
# -------------------------------------------------------
|
| 219 |
+
# CHECK 11 — FLAG without revealed signals gives penalty
|
| 220 |
+
# -------------------------------------------------------
|
| 221 |
+
echo ""
|
| 222 |
+
echo "CHECK 11: FLAG without any revealed signals must give -0.15 penalty"
|
| 223 |
+
curl -s -X POST "$BASE/reset" \
|
| 224 |
+
-H "Content-Type: application/json" \
|
| 225 |
+
-d '{"task": "easy"}' > /dev/null
|
| 226 |
+
|
| 227 |
+
curl -s -X POST "$BASE/step" \
|
| 228 |
+
-H "Content-Type: application/json" \
|
| 229 |
+
-d "{\"action_type\": \"flag\", \"account_id\": \"$ACCOUNT_ID\"}" | \
|
| 230 |
+
python3 -c "
|
| 231 |
+
import sys, json
|
| 232 |
+
d = json.load(sys.stdin)
|
| 233 |
+
reward = d.get('reward', None)
|
| 234 |
+
print(f' reward = {reward}')
|
| 235 |
+
print(' PASS — penalty applied for flagging without evidence' if reward and float(reward) <= -0.10 else
|
| 236 |
+
f' WARN — reward={reward}, expected -0.15 penalty')
|
| 237 |
+
"
|
| 238 |
+
|
| 239 |
+
# -------------------------------------------------------
|
| 240 |
+
# CHECK 12 — Full episode end to end
|
| 241 |
+
# -------------------------------------------------------
|
| 242 |
+
echo ""
|
| 243 |
+
echo "CHECK 12: Full episode — reset, get_policy, tools, flag, submit"
|
| 244 |
+
python3 - <<'PYEOF'
|
| 245 |
+
import requests, json
|
| 246 |
+
|
| 247 |
+
BASE = "http://localhost:7860"
|
| 248 |
+
|
| 249 |
+
# Reset
|
| 250 |
+
r = requests.post(f"{BASE}/reset", json={"task": "easy"})
|
| 251 |
+
resp = r.json()
|
| 252 |
+
obs = resp.get("observation", resp)
|
| 253 |
+
accounts = obs.get("visible_account_ids") or obs.get("visible_accounts") or []
|
| 254 |
+
if accounts:
|
| 255 |
+
acc = accounts[0] if isinstance(accounts[0], str) else accounts[0].get("account_id")
|
| 256 |
+
else:
|
| 257 |
+
acc = "acc_000"
|
| 258 |
+
print(f" Episode started. First account: {acc}")
|
| 259 |
+
|
| 260 |
+
steps = []
|
| 261 |
+
|
| 262 |
+
# Step 1: get_policy
|
| 263 |
+
r = requests.post(f"{BASE}/step", json={"action_type": "get_policy"})
|
| 264 |
+
d = r.json()
|
| 265 |
+
steps.append(("get_policy", d.get("reward")))
|
| 266 |
+
|
| 267 |
+
# Step 2: reverse_image_search
|
| 268 |
+
r = requests.post(f"{BASE}/step", json={"action_type": "reverse_image_search", "account_id": acc})
|
| 269 |
+
d = r.json()
|
| 270 |
+
steps.append(("reverse_image_search", d.get("reward")))
|
| 271 |
+
|
| 272 |
+
# Step 3: analyze_bio
|
| 273 |
+
r = requests.post(f"{BASE}/step", json={"action_type": "analyze_bio", "account_id": acc})
|
| 274 |
+
d = r.json()
|
| 275 |
+
steps.append(("analyze_bio", d.get("reward")))
|
| 276 |
+
|
| 277 |
+
# Step 4: flag
|
| 278 |
+
r = requests.post(f"{BASE}/step", json={"action_type": "flag", "account_id": acc})
|
| 279 |
+
d = r.json()
|
| 280 |
+
steps.append(("flag", d.get("reward")))
|
| 281 |
+
|
| 282 |
+
# Step 5: submit
|
| 283 |
+
r = requests.post(f"{BASE}/step", json={"action_type": "submit"})
|
| 284 |
+
d = r.json()
|
| 285 |
+
steps.append(("submit", d.get("reward")))
|
| 286 |
+
msg = d.get("message", "")
|
| 287 |
+
done = d.get("done", False)
|
| 288 |
+
|
| 289 |
+
print("\n Action log:")
|
| 290 |
+
for action, reward in steps:
|
| 291 |
+
print(f" {action:<25} reward={reward}")
|
| 292 |
+
|
| 293 |
+
print(f"\n Episode done: {done}")
|
| 294 |
+
|
| 295 |
+
# Check decision package in submit message
|
| 296 |
+
for keyword in ["Decision:", "policy_rationale", "evidence_summary", "flagged_accounts"]:
|
| 297 |
+
found = keyword.lower() in msg.lower()
|
| 298 |
+
print(f" Decision package [{keyword}]: {'PASS' if found else 'MISSING'}")
|
| 299 |
+
|
| 300 |
+
# Check grader score
|
| 301 |
+
grader = d.get("grader_score") or d.get("score")
|
| 302 |
+
print(f" Grader score: {grader}")
|
| 303 |
+
|
| 304 |
+
# Check structured decision_package field
|
| 305 |
+
dp = d.get("decision_package")
|
| 306 |
+
if dp:
|
| 307 |
+
print(f" decision_package keys: {sorted(dp.keys())}")
|
| 308 |
+
else:
|
| 309 |
+
print(" decision_package: missing (expected after submit)")
|
| 310 |
+
PYEOF
|
| 311 |
+
|
| 312 |
+
# -------------------------------------------------------
|
| 313 |
+
# SUMMARY
|
| 314 |
+
# -------------------------------------------------------
|
| 315 |
+
echo ""
|
| 316 |
+
echo "========================================"
|
| 317 |
+
echo "CHECK COMPLETE"
|
| 318 |
+
echo ""
|
| 319 |
+
echo "Expected results:"
|
| 320 |
+
echo " CHECK 1-3: Server healthy, tasks listed, reset works"
|
| 321 |
+
echo " CHECK 4: get_policy returns threshold from compiled policy"
|
| 322 |
+
echo " CHECK 5: inspect works"
|
| 323 |
+
echo " CHECK 6: reverse_image_search reveals photo_reuse_score"
|
| 324 |
+
echo " CHECK 7: analyze_bio reveals bio_template_score"
|
| 325 |
+
echo " CHECK 8: check_ip reveals ip_cluster_signal"
|
| 326 |
+
echo " CHECK 9: get_policy at step 0 gives +0.20 reward"
|
| 327 |
+
echo " CHECK 10: redundant tool call gives -0.05 penalty"
|
| 328 |
+
echo " CHECK 11: flag without evidence gives -0.15 penalty"
|
| 329 |
+
echo " CHECK 12: full episode runs, decision package in submit"
|
| 330 |
+
echo ""
|
| 331 |
+
echo "If any check fails, fix that component before running eval scripts."
|
| 332 |
+
echo "========================================"
|
client.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Python client for the Fake Gang Detection OpenEnv environment."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from typing import Any, Dict, Optional
|
| 8 |
+
|
| 9 |
+
try:
|
| 10 |
+
import requests
|
| 11 |
+
except ImportError:
|
| 12 |
+
requests = None # type: ignore
|
| 13 |
+
|
| 14 |
+
from models import (
|
| 15 |
+
AccountProfile,
|
| 16 |
+
FakeGangAction,
|
| 17 |
+
FakeGangObservation,
|
| 18 |
+
FakeGangState,
|
| 19 |
+
ActionType,
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
# ---------------------------------------------------------------------------
|
| 24 |
+
# Result container
|
| 25 |
+
# ---------------------------------------------------------------------------
|
| 26 |
+
|
| 27 |
+
@dataclass
|
| 28 |
+
class StepResult:
|
| 29 |
+
observation: FakeGangObservation
|
| 30 |
+
done: bool
|
| 31 |
+
reward: Optional[float]
|
| 32 |
+
message: str
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
# ---------------------------------------------------------------------------
|
| 36 |
+
# Sync HTTP client
|
| 37 |
+
# ---------------------------------------------------------------------------
|
| 38 |
+
|
| 39 |
+
class FakeGangEnvClient:
|
| 40 |
+
"""Synchronous HTTP client for the Fake Gang Detection environment."""
|
| 41 |
+
|
| 42 |
+
def __init__(self, base_url: str = "http://localhost:8000") -> None:
|
| 43 |
+
if requests is None:
|
| 44 |
+
raise ImportError("Install 'requests' to use FakeGangEnvClient.")
|
| 45 |
+
self.base_url = base_url.rstrip("/")
|
| 46 |
+
self._session = requests.Session()
|
| 47 |
+
|
| 48 |
+
# ------------------------------------------------------------------
|
| 49 |
+
# Public API
|
| 50 |
+
# ------------------------------------------------------------------
|
| 51 |
+
|
| 52 |
+
def reset(
|
| 53 |
+
self,
|
| 54 |
+
task: str = "easy",
|
| 55 |
+
seed: Optional[int] = None,
|
| 56 |
+
episode_id: Optional[str] = None,
|
| 57 |
+
) -> StepResult:
|
| 58 |
+
payload = {"task": task}
|
| 59 |
+
if seed is not None:
|
| 60 |
+
payload["seed"] = seed
|
| 61 |
+
if episode_id is not None:
|
| 62 |
+
payload["episode_id"] = episode_id
|
| 63 |
+
resp = self._post("/reset", payload)
|
| 64 |
+
return self._parse_result(resp)
|
| 65 |
+
|
| 66 |
+
def step(self, action: FakeGangAction) -> StepResult:
|
| 67 |
+
resp = self._post("/step", action.model_dump())
|
| 68 |
+
return self._parse_result(resp)
|
| 69 |
+
|
| 70 |
+
def state(self) -> FakeGangState:
|
| 71 |
+
resp = self._session.get(f"{self.base_url}/state")
|
| 72 |
+
resp.raise_for_status()
|
| 73 |
+
return FakeGangState(**resp.json())
|
| 74 |
+
|
| 75 |
+
def health(self) -> Dict[str, str]:
|
| 76 |
+
resp = self._session.get(f"{self.base_url}/health")
|
| 77 |
+
resp.raise_for_status()
|
| 78 |
+
return resp.json()
|
| 79 |
+
|
| 80 |
+
# ------------------------------------------------------------------
|
| 81 |
+
# Convenience shortcuts
|
| 82 |
+
# ------------------------------------------------------------------
|
| 83 |
+
|
| 84 |
+
def inspect(self, account_id: str) -> StepResult:
|
| 85 |
+
return self.step(FakeGangAction(action_type=ActionType.INSPECT, account_id=account_id))
|
| 86 |
+
|
| 87 |
+
def investigate_network(self, account_id: str) -> StepResult:
|
| 88 |
+
return self.step(FakeGangAction(action_type=ActionType.INVESTIGATE_NETWORK, account_id=account_id))
|
| 89 |
+
|
| 90 |
+
def flag(self, account_id: str) -> StepResult:
|
| 91 |
+
return self.step(FakeGangAction(action_type=ActionType.FLAG, account_id=account_id))
|
| 92 |
+
|
| 93 |
+
def unflag(self, account_id: str) -> StepResult:
|
| 94 |
+
return self.step(FakeGangAction(action_type=ActionType.UNFLAG, account_id=account_id))
|
| 95 |
+
|
| 96 |
+
def submit(self) -> StepResult:
|
| 97 |
+
return self.step(FakeGangAction(action_type=ActionType.SUBMIT))
|
| 98 |
+
|
| 99 |
+
# ------------------------------------------------------------------
|
| 100 |
+
# Helpers
|
| 101 |
+
# ------------------------------------------------------------------
|
| 102 |
+
|
| 103 |
+
def _post(self, path: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
| 104 |
+
resp = self._session.post(f"{self.base_url}{path}", json=payload)
|
| 105 |
+
resp.raise_for_status()
|
| 106 |
+
return resp.json()
|
| 107 |
+
|
| 108 |
+
def _parse_result(self, payload: Dict[str, Any]) -> StepResult:
|
| 109 |
+
obs_data = payload["observation"]
|
| 110 |
+
profiles = [AccountProfile(**p) for p in obs_data.get("visible_accounts", [])]
|
| 111 |
+
obs = FakeGangObservation(
|
| 112 |
+
done=obs_data.get("done", False),
|
| 113 |
+
reward=obs_data.get("reward"),
|
| 114 |
+
visible_accounts=profiles,
|
| 115 |
+
visible_account_ids=obs_data.get("visible_account_ids", []),
|
| 116 |
+
flagged_ids=obs_data.get("flagged_ids", []),
|
| 117 |
+
inspected_ids=obs_data.get("inspected_ids", []),
|
| 118 |
+
graph_edges=obs_data.get("graph_edges", {}),
|
| 119 |
+
steps_remaining=obs_data.get("steps_remaining", 0),
|
| 120 |
+
evasion_triggered=obs_data.get("evasion_triggered", False),
|
| 121 |
+
evasion_count=obs_data.get("evasion_count", 0),
|
| 122 |
+
task=obs_data.get("task", "easy"),
|
| 123 |
+
message=obs_data.get("message", ""),
|
| 124 |
+
suspect_ids=obs_data.get("suspect_ids", []),
|
| 125 |
+
)
|
| 126 |
+
return StepResult(
|
| 127 |
+
observation=obs,
|
| 128 |
+
done=payload.get("done", False),
|
| 129 |
+
reward=payload.get("reward"),
|
| 130 |
+
message=payload.get("message", ""),
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
# ------------------------------------------------------------------
|
| 134 |
+
# Context manager support
|
| 135 |
+
# ------------------------------------------------------------------
|
| 136 |
+
|
| 137 |
+
def __enter__(self) -> "FakeGangEnvClient":
|
| 138 |
+
return self
|
| 139 |
+
|
| 140 |
+
def __exit__(self, *args: Any) -> None:
|
| 141 |
+
self._session.close()
|
dashboard/README.md
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Fake Gang Detection Dashboard
|
| 2 |
+
|
| 3 |
+
Interactive visualization dashboard for Round 2 platform-adaptive detection.
|
| 4 |
+
|
| 5 |
+
**Tech Stack**: React 18 + TypeScript + D3.js + FastAPI + WebSockets
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Setup
|
| 10 |
+
|
| 11 |
+
### Backend (FastAPI)
|
| 12 |
+
|
| 13 |
+
```bash
|
| 14 |
+
cd dashboard/backend
|
| 15 |
+
pip install -r requirements.txt
|
| 16 |
+
python main.py
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
Backend runs on `http://localhost:8000`
|
| 20 |
+
|
| 21 |
+
API docs: `http://localhost:8000/docs`
|
| 22 |
+
|
| 23 |
+
### Frontend (React + Vite)
|
| 24 |
+
|
| 25 |
+
```bash
|
| 26 |
+
cd dashboard/frontend
|
| 27 |
+
npm install
|
| 28 |
+
npm run dev
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
Frontend runs on `http://localhost:5173`
|
| 32 |
+
|
| 33 |
+
---
|
| 34 |
+
|
| 35 |
+
## Features
|
| 36 |
+
|
| 37 |
+
### 1. Policy Compiler Panel
|
| 38 |
+
- Select platform (Instagram/Snapchat)
|
| 39 |
+
- Click "Compile Policy" to run Bayesian threshold calculation
|
| 40 |
+
- Streaming progress via WebSocket
|
| 41 |
+
- Policy summary card with:
|
| 42 |
+
- Threshold (θ*) with STRICT/LENIENT badge
|
| 43 |
+
- Base rate (π)
|
| 44 |
+
- FP penalty weight with HIGH/LOW badge
|
| 45 |
+
- Primary enforcement signal
|
| 46 |
+
- FN/FP cost signals
|
| 47 |
+
- Confidence score
|
| 48 |
+
- Compilation timestamp
|
| 49 |
+
|
| 50 |
+
### 2. Network Graph Panel
|
| 51 |
+
- D3.js force-directed layout
|
| 52 |
+
- Node colors:
|
| 53 |
+
- 🔴 Red: Gang members (confirmed fakes)
|
| 54 |
+
- 🟢 Green: Real accounts
|
| 55 |
+
- 🟣 Purple: Celebrities (high hub legitimacy)
|
| 56 |
+
- Edge styles:
|
| 57 |
+
- Solid: Follows relationship
|
| 58 |
+
- Dashed: Mutual follows
|
| 59 |
+
- Interactive:
|
| 60 |
+
- Drag nodes to reposition
|
| 61 |
+
- Zoom and pan
|
| 62 |
+
- Hover for tooltips (account ID, risk, hub score)
|
| 63 |
+
- Auto-refreshes every 2 seconds during episode
|
| 64 |
+
|
| 65 |
+
### 3. Training Panel
|
| 66 |
+
- Platform comparison (Instagram vs Snapchat)
|
| 67 |
+
- Metrics:
|
| 68 |
+
- Episode count
|
| 69 |
+
- Average score
|
| 70 |
+
- Average precision
|
| 71 |
+
- Average recall
|
| 72 |
+
- Recent episodes list with:
|
| 73 |
+
- Episode number and platform
|
| 74 |
+
- Score (color-coded: green >0.85, yellow <0.85)
|
| 75 |
+
- TP/FP/FN breakdown
|
| 76 |
+
|
| 77 |
+
### 4. Control Panel
|
| 78 |
+
- **Single Episode**:
|
| 79 |
+
- Platform selector
|
| 80 |
+
- Task difficulty (easy/medium/hard)
|
| 81 |
+
- Seed input
|
| 82 |
+
- Start/Stop buttons
|
| 83 |
+
- **Batch Training**:
|
| 84 |
+
- Episode count input
|
| 85 |
+
- Auto-split between Instagram/Snapchat
|
| 86 |
+
- Run training button
|
| 87 |
+
|
| 88 |
+
---
|
| 89 |
+
|
| 90 |
+
## API Endpoints
|
| 91 |
+
|
| 92 |
+
### WebSocket
|
| 93 |
+
|
| 94 |
+
- `WS /ws/compile_policy/{platform}` - Policy compilation progress
|
| 95 |
+
- `WS /ws/episode/{episode_id}` - Episode state updates
|
| 96 |
+
- `WS /ws/training/{training_id}` - Training progress
|
| 97 |
+
|
| 98 |
+
### REST
|
| 99 |
+
|
| 100 |
+
- `POST /api/compile_policy` - Compile policy (non-streaming)
|
| 101 |
+
- `GET /api/policy/{platform}` - Get cached policy
|
| 102 |
+
- `POST /api/episode/start` - Start new episode
|
| 103 |
+
- `POST /api/episode/{episode_id}/step` - Execute action
|
| 104 |
+
- `GET /api/episode/{episode_id}/state` - Get episode state
|
| 105 |
+
- `POST /api/training/start` - Start training loop
|
| 106 |
+
- `GET /api/training/{training_id}/metrics` - Get training metrics
|
| 107 |
+
- `GET /health` - Health check
|
| 108 |
+
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
## Development
|
| 112 |
+
|
| 113 |
+
### Backend Development
|
| 114 |
+
|
| 115 |
+
```bash
|
| 116 |
+
cd dashboard/backend
|
| 117 |
+
|
| 118 |
+
# Run with auto-reload
|
| 119 |
+
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
| 120 |
+
|
| 121 |
+
# Test policy endpoint
|
| 122 |
+
curl http://localhost:8000/api/policy/Instagram
|
| 123 |
+
|
| 124 |
+
# Test episode start
|
| 125 |
+
curl -X POST http://localhost:8000/api/episode/start \
|
| 126 |
+
-H "Content-Type: application/json" \
|
| 127 |
+
-d '{"platform": "Instagram", "task": "easy", "seed": 0}'
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
### Frontend Development
|
| 131 |
+
|
| 132 |
+
```bash
|
| 133 |
+
cd dashboard/frontend
|
| 134 |
+
|
| 135 |
+
# Install dependencies
|
| 136 |
+
npm install
|
| 137 |
+
|
| 138 |
+
# Run dev server with hot reload
|
| 139 |
+
npm run dev
|
| 140 |
+
|
| 141 |
+
# Build for production
|
| 142 |
+
npm run build
|
| 143 |
+
|
| 144 |
+
# Preview production build
|
| 145 |
+
npm run preview
|
| 146 |
+
|
| 147 |
+
# Lint code
|
| 148 |
+
npm run lint
|
| 149 |
+
```
|
| 150 |
+
|
| 151 |
+
### Adding New Components
|
| 152 |
+
|
| 153 |
+
1. Create component file in `src/components/`
|
| 154 |
+
2. Import in `App.tsx`
|
| 155 |
+
3. Add to panels grid or control panel
|
| 156 |
+
|
| 157 |
+
Example:
|
| 158 |
+
```typescript
|
| 159 |
+
// src/components/MetricsChart.tsx
|
| 160 |
+
import { useEffect, useRef } from 'react'
|
| 161 |
+
import * as d3 from 'd3'
|
| 162 |
+
|
| 163 |
+
export default function MetricsChart() {
|
| 164 |
+
const svgRef = useRef<SVGSVGElement>(null)
|
| 165 |
+
|
| 166 |
+
useEffect(() => {
|
| 167 |
+
// D3.js rendering logic
|
| 168 |
+
}, [])
|
| 169 |
+
|
| 170 |
+
return <svg ref={svgRef} />
|
| 171 |
+
}
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
---
|
| 175 |
+
|
| 176 |
+
## Architecture
|
| 177 |
+
|
| 178 |
+
```
|
| 179 |
+
┌─────────────────────────────────────────────────────────────┐
|
| 180 |
+
│ React Frontend │
|
| 181 |
+
│ ┌───────────────┐ ┌────────────────┐ ┌────────────────┐ │
|
| 182 |
+
│ │ PolicyPanel │ │ NetworkGraph │ │ TrainingPanel │ │
|
| 183 |
+
│ │ (streaming) │ │ (D3.js force) │ │ (metrics) │ │
|
| 184 |
+
│ └───────────────┘ └────────────────┘ └────────────────┘ │
|
| 185 |
+
│ │ │ │ │
|
| 186 |
+
│ └─────────────────┴──────────────────┘ │
|
| 187 |
+
│ │ │
|
| 188 |
+
│ WebSocket + REST API │
|
| 189 |
+
└─────────────────────────────��┬────────────────────────────────┘
|
| 190 |
+
│
|
| 191 |
+
┌──────────────────────────────┴────────────────────────────────┐
|
| 192 |
+
│ FastAPI Backend │
|
| 193 |
+
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
|
| 194 |
+
│ │ Policy Compiler│ │ Environment │ │ Episode State │ │
|
| 195 |
+
│ │ (WebSocket) │ │ (OpenEnv) │ │ Management │ │
|
| 196 |
+
│ └────────────────┘ └────────────────┘ └────────────────┘ │
|
| 197 |
+
└───────────────────────────────────────────────────────────────┘
|
| 198 |
+
```
|
| 199 |
+
|
| 200 |
+
### Data Flow
|
| 201 |
+
|
| 202 |
+
**Policy Compilation**:
|
| 203 |
+
1. User selects platform and clicks "Compile Policy"
|
| 204 |
+
2. Frontend opens WebSocket to `/ws/compile_policy/{platform}`
|
| 205 |
+
3. Backend calls `policy_compiler.get_policy(platform)`
|
| 206 |
+
4. Progress streamed via WebSocket: `{type: "progress", message: "..."}`
|
| 207 |
+
5. Complete policy sent: `{type: "complete", policy: {...}}`
|
| 208 |
+
6. Frontend renders PolicyCard with metrics
|
| 209 |
+
|
| 210 |
+
**Episode Execution**:
|
| 211 |
+
1. User sets parameters and clicks "Start Episode"
|
| 212 |
+
2. Frontend POSTs to `/api/episode/start`
|
| 213 |
+
3. Backend creates `FakeGangEnvironment`, calls `reset()`
|
| 214 |
+
4. Returns `{episode_id, platform, observation, policy}`
|
| 215 |
+
5. Frontend polls `/api/episode/{episode_id}/state` every 2s
|
| 216 |
+
6. Returns `{observation, graph_data, metrics}`
|
| 217 |
+
7. NetworkGraph updates with new nodes/edges
|
| 218 |
+
|
| 219 |
+
**Training Loop** (TODO: integrate with agent/train.py):
|
| 220 |
+
1. User sets episode count and clicks "Run Training"
|
| 221 |
+
2. Frontend POSTs to `/api/training/start`
|
| 222 |
+
3. Backend starts training loop in background
|
| 223 |
+
4. For each episode, sends WebSocket update: `{type: "episode_complete", ...}`
|
| 224 |
+
5. Frontend updates TrainingPanel metrics in real-time
|
| 225 |
+
|
| 226 |
+
---
|
| 227 |
+
|
| 228 |
+
## Next Steps
|
| 229 |
+
|
| 230 |
+
### Phase 1: Core Functionality ✅
|
| 231 |
+
- [x] PolicyPanel with WebSocket streaming
|
| 232 |
+
- [x] NetworkGraphPanel with D3.js force layout
|
| 233 |
+
- [x] TrainingPanel with metrics display
|
| 234 |
+
- [x] ControlPanel for episode/training control
|
| 235 |
+
- [x] FastAPI backend with REST + WebSocket
|
| 236 |
+
|
| 237 |
+
### Phase 2: Integration 📋
|
| 238 |
+
- [ ] Connect training loop to `agent/train.py`
|
| 239 |
+
- [ ] Implement real-time episode stepping (manual mode)
|
| 240 |
+
- [ ] Add WebSocket reconnection logic
|
| 241 |
+
- [ ] Store training metrics in backend state
|
| 242 |
+
|
| 243 |
+
### Phase 3: Enhancements 📋
|
| 244 |
+
- [ ] Training curve line chart (D3.js)
|
| 245 |
+
- [ ] Tool usage breakdown bar chart
|
| 246 |
+
- [ ] Precision/Recall scatter plot
|
| 247 |
+
- [ ] Export metrics to JSON/CSV
|
| 248 |
+
- [ ] Dark/light theme toggle
|
| 249 |
+
- [ ] Responsive design for mobile
|
| 250 |
+
|
| 251 |
+
### Phase 4: Advanced Features 📋
|
| 252 |
+
- [ ] Multi-model comparison (Qwen vs Claude vs Llama)
|
| 253 |
+
- [ ] Episode replay (step-by-step visualization)
|
| 254 |
+
- [ ] Gang registry visualization
|
| 255 |
+
- [ ] Custom policy editor
|
| 256 |
+
- [ ] A/B testing framework
|
| 257 |
+
|
| 258 |
+
---
|
| 259 |
+
|
| 260 |
+
## Troubleshooting
|
| 261 |
+
|
| 262 |
+
**Backend fails to start**:
|
| 263 |
+
- Check Python version (3.10+)
|
| 264 |
+
- Install requirements: `pip install -r requirements.txt`
|
| 265 |
+
- Check port 8000 is available: `lsof -i :8000`
|
| 266 |
+
|
| 267 |
+
**Frontend fails to build**:
|
| 268 |
+
- Check Node version (18+)
|
| 269 |
+
- Delete `node_modules` and reinstall: `rm -rf node_modules && npm install`
|
| 270 |
+
- Clear cache: `rm -rf .vite`
|
| 271 |
+
|
| 272 |
+
**WebSocket connection fails**:
|
| 273 |
+
- Check backend is running on port 8000
|
| 274 |
+
- Check CORS settings in `main.py`
|
| 275 |
+
- Open browser console for errors
|
| 276 |
+
|
| 277 |
+
**Graph not rendering**:
|
| 278 |
+
- Check D3.js version: `npm list d3`
|
| 279 |
+
- Check browser console for errors
|
| 280 |
+
- Verify `graphData` is populated: `console.log(graphData)`
|
| 281 |
+
|
| 282 |
+
**Policy not loading**:
|
| 283 |
+
- Check `policy_cache/` directory exists
|
| 284 |
+
- Verify Tavily/Groq API keys (or use fallback)
|
| 285 |
+
- Check backend logs for compilation errors
|
| 286 |
+
|
| 287 |
+
---
|
| 288 |
+
|
| 289 |
+
## Production Deployment
|
| 290 |
+
|
| 291 |
+
### Backend
|
| 292 |
+
|
| 293 |
+
```bash
|
| 294 |
+
# Install production dependencies
|
| 295 |
+
pip install gunicorn
|
| 296 |
+
|
| 297 |
+
# Run with Gunicorn (production WSGI server)
|
| 298 |
+
gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app --bind 0.0.0.0:8000
|
| 299 |
+
```
|
| 300 |
+
|
| 301 |
+
### Frontend
|
| 302 |
+
|
| 303 |
+
```bash
|
| 304 |
+
# Build production bundle
|
| 305 |
+
npm run build
|
| 306 |
+
|
| 307 |
+
# Serve with nginx or host on Vercel/Netlify
|
| 308 |
+
# Output in dist/ directory
|
| 309 |
+
```
|
| 310 |
+
|
| 311 |
+
### Docker (TODO)
|
| 312 |
+
|
| 313 |
+
```dockerfile
|
| 314 |
+
# Dockerfile
|
| 315 |
+
FROM python:3.10-slim
|
| 316 |
+
WORKDIR /app
|
| 317 |
+
COPY requirements.txt .
|
| 318 |
+
RUN pip install -r requirements.txt
|
| 319 |
+
COPY . .
|
| 320 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
| 321 |
+
```
|
| 322 |
+
|
| 323 |
+
---
|
| 324 |
+
|
| 325 |
+
**Dashboard Status**: ✅ Core implementation complete, ready for integration testing
|
| 326 |
+
|
| 327 |
+
**Next**: Connect training loop and test end-to-end flow
|
dashboard/backend/main.py
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
FastAPI Backend for Fake Gang Detection Dashboard
|
| 3 |
+
|
| 4 |
+
Provides:
|
| 5 |
+
- WebSocket streaming for policy compilation, episodes, and training
|
| 6 |
+
- REST endpoints for episode control and metrics
|
| 7 |
+
- CORS support for React frontend
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import asyncio
|
| 11 |
+
import json
|
| 12 |
+
import uuid
|
| 13 |
+
from datetime import datetime
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
from typing import Dict, List, Optional
|
| 16 |
+
|
| 17 |
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException
|
| 18 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 19 |
+
from pydantic import BaseModel
|
| 20 |
+
import sys
|
| 21 |
+
|
| 22 |
+
# Add parent to path
|
| 23 |
+
_ROOT = Path(__file__).parent.parent.parent
|
| 24 |
+
sys.path.insert(0, str(_ROOT))
|
| 25 |
+
|
| 26 |
+
from server.environment import FakeGangEnvironment
|
| 27 |
+
from server.policy_compiler import compile_policy, get_policy
|
| 28 |
+
from models import FakeGangAction, ActionType
|
| 29 |
+
|
| 30 |
+
# ============================================================================
|
| 31 |
+
# Lifespan Context Manager
|
| 32 |
+
# ============================================================================
|
| 33 |
+
|
| 34 |
+
from contextlib import asynccontextmanager
|
| 35 |
+
|
| 36 |
+
@asynccontextmanager
|
| 37 |
+
async def lifespan(app_instance: FastAPI):
|
| 38 |
+
"""Lifespan context manager for startup/shutdown."""
|
| 39 |
+
# Startup
|
| 40 |
+
print("🚀 Starting Fake Gang Detection Dashboard API")
|
| 41 |
+
print("📍 API: http://localhost:8000")
|
| 42 |
+
print("📖 Docs: http://localhost:8000/docs")
|
| 43 |
+
|
| 44 |
+
# Pre-load policies
|
| 45 |
+
for platform in ["Instagram", "Snapchat"]:
|
| 46 |
+
try:
|
| 47 |
+
policy = get_policy(platform)
|
| 48 |
+
policy_cache[platform] = policy.model_dump()
|
| 49 |
+
print(f"✓ Loaded {platform} policy: θ={policy.threshold:.3f}")
|
| 50 |
+
except Exception as e:
|
| 51 |
+
print(f"⚠ Failed to load {platform} policy: {e}")
|
| 52 |
+
|
| 53 |
+
yield
|
| 54 |
+
|
| 55 |
+
# Shutdown
|
| 56 |
+
print("👋 Shutting down Dashboard API")
|
| 57 |
+
|
| 58 |
+
# ============================================================================
|
| 59 |
+
# FastAPI App
|
| 60 |
+
# ============================================================================
|
| 61 |
+
|
| 62 |
+
app = FastAPI(title="Fake Gang Detection Dashboard API", lifespan=lifespan)
|
| 63 |
+
|
| 64 |
+
# CORS for React frontend
|
| 65 |
+
app.add_middleware(
|
| 66 |
+
CORSMiddleware,
|
| 67 |
+
allow_origins=["http://localhost:5173", "http://localhost:3000"], # Vite default, CRA default
|
| 68 |
+
allow_credentials=True,
|
| 69 |
+
allow_methods=["*"],
|
| 70 |
+
allow_headers=["*"],
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
# ============================================================================
|
| 74 |
+
# Global State
|
| 75 |
+
# ============================================================================
|
| 76 |
+
|
| 77 |
+
active_episodes: Dict[str, Dict] = {} # episode_id -> {env, obs, task, seed, platform}
|
| 78 |
+
active_trainings: Dict[str, Dict] = {} # training_id -> {status, results, ...}
|
| 79 |
+
policy_cache: Dict[str, Dict] = {} # platform -> policy
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
# ============================================================================
|
| 83 |
+
# Request/Response Models
|
| 84 |
+
# ============================================================================
|
| 85 |
+
|
| 86 |
+
class CompilePolicyRequest(BaseModel):
|
| 87 |
+
platform: str # "Instagram" or "Snapchat"
|
| 88 |
+
use_cache: bool = True
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class StartEpisodeRequest(BaseModel):
|
| 92 |
+
platform: Optional[str] = None # Auto-assign by seed if None
|
| 93 |
+
task: str = "easy"
|
| 94 |
+
seed: int = 0
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
class StepActionRequest(BaseModel):
|
| 98 |
+
action_type: str
|
| 99 |
+
account_id: Optional[str] = None
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
class StartTrainingRequest(BaseModel):
|
| 103 |
+
episodes: int = 50
|
| 104 |
+
task: str = "easy"
|
| 105 |
+
platforms: Optional[List[str]] = None # ["Instagram", "Snapchat"] or None for auto
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
# ============================================================================
|
| 109 |
+
# WebSocket: Policy Compilation
|
| 110 |
+
# ============================================================================
|
| 111 |
+
|
| 112 |
+
@app.websocket("/ws/compile_policy/{platform}")
|
| 113 |
+
async def websocket_compile_policy(websocket: WebSocket, platform: str):
|
| 114 |
+
"""Stream policy compilation progress."""
|
| 115 |
+
await websocket.accept()
|
| 116 |
+
|
| 117 |
+
try:
|
| 118 |
+
# Send progress updates
|
| 119 |
+
await websocket.send_json({
|
| 120 |
+
"type": "progress",
|
| 121 |
+
"message": f"🔍 Starting policy compilation for {platform}..."
|
| 122 |
+
})
|
| 123 |
+
|
| 124 |
+
await asyncio.sleep(0.5)
|
| 125 |
+
|
| 126 |
+
await websocket.send_json({
|
| 127 |
+
"type": "progress",
|
| 128 |
+
"message": "🌐 Fetching transparency reports from Tavily..."
|
| 129 |
+
})
|
| 130 |
+
|
| 131 |
+
await asyncio.sleep(1)
|
| 132 |
+
|
| 133 |
+
await websocket.send_json({
|
| 134 |
+
"type": "progress",
|
| 135 |
+
"message": "🤖 Extracting parameters with Groq LLM..."
|
| 136 |
+
})
|
| 137 |
+
|
| 138 |
+
await asyncio.sleep(1.5)
|
| 139 |
+
|
| 140 |
+
await websocket.send_json({
|
| 141 |
+
"type": "progress",
|
| 142 |
+
"message": "📊 Computing Bayesian threshold..."
|
| 143 |
+
})
|
| 144 |
+
|
| 145 |
+
# Actually compile policy (uses cache if available)
|
| 146 |
+
policy = get_policy(platform)
|
| 147 |
+
|
| 148 |
+
await websocket.send_json({
|
| 149 |
+
"type": "progress",
|
| 150 |
+
"message": f"✅ Policy compiled: θ={policy.threshold:.3f}"
|
| 151 |
+
})
|
| 152 |
+
|
| 153 |
+
await asyncio.sleep(0.5)
|
| 154 |
+
|
| 155 |
+
# Send complete policy
|
| 156 |
+
await websocket.send_json({
|
| 157 |
+
"type": "complete",
|
| 158 |
+
"policy": policy.model_dump(),
|
| 159 |
+
})
|
| 160 |
+
|
| 161 |
+
except WebSocketDisconnect:
|
| 162 |
+
pass
|
| 163 |
+
except Exception as e:
|
| 164 |
+
await websocket.send_json({
|
| 165 |
+
"type": "error",
|
| 166 |
+
"message": str(e),
|
| 167 |
+
})
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
# ============================================================================
|
| 171 |
+
# WebSocket: Episode Execution
|
| 172 |
+
# ============================================================================
|
| 173 |
+
|
| 174 |
+
@app.websocket("/ws/episode/{episode_id}")
|
| 175 |
+
async def websocket_episode(websocket: WebSocket, episode_id: str):
|
| 176 |
+
"""Stream episode state updates."""
|
| 177 |
+
await websocket.accept()
|
| 178 |
+
|
| 179 |
+
if episode_id not in active_episodes:
|
| 180 |
+
await websocket.send_json({
|
| 181 |
+
"type": "error",
|
| 182 |
+
"message": f"Episode {episode_id} not found"
|
| 183 |
+
})
|
| 184 |
+
return
|
| 185 |
+
|
| 186 |
+
ep_data = active_episodes[episode_id]
|
| 187 |
+
env = ep_data["env"]
|
| 188 |
+
obs = ep_data["obs"]
|
| 189 |
+
|
| 190 |
+
try:
|
| 191 |
+
# Send initial state
|
| 192 |
+
await websocket.send_json({
|
| 193 |
+
"type": "observation",
|
| 194 |
+
"data": obs.model_dump(),
|
| 195 |
+
})
|
| 196 |
+
|
| 197 |
+
# Wait for actions from client (if manual mode)
|
| 198 |
+
# For now, we'll just send the current state
|
| 199 |
+
while True:
|
| 200 |
+
await asyncio.sleep(1)
|
| 201 |
+
# In a real implementation, this would wait for step actions
|
| 202 |
+
|
| 203 |
+
except WebSocketDisconnect:
|
| 204 |
+
pass
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
# ============================================================================
|
| 208 |
+
# WebSocket: Training Loop
|
| 209 |
+
# ============================================================================
|
| 210 |
+
|
| 211 |
+
@app.websocket("/ws/training/{training_id}")
|
| 212 |
+
async def websocket_training(websocket: WebSocket, training_id: str):
|
| 213 |
+
"""Stream training progress."""
|
| 214 |
+
await websocket.accept()
|
| 215 |
+
|
| 216 |
+
if training_id not in active_trainings:
|
| 217 |
+
await websocket.send_json({
|
| 218 |
+
"type": "error",
|
| 219 |
+
"message": f"Training {training_id} not found"
|
| 220 |
+
})
|
| 221 |
+
return
|
| 222 |
+
|
| 223 |
+
training_data = active_trainings[training_id]
|
| 224 |
+
|
| 225 |
+
try:
|
| 226 |
+
# Stream training progress (simulated here)
|
| 227 |
+
# In real implementation, this would run the actual training loop
|
| 228 |
+
await websocket.send_json({
|
| 229 |
+
"type": "started",
|
| 230 |
+
"episodes": training_data["episodes"],
|
| 231 |
+
"task": training_data["task"],
|
| 232 |
+
})
|
| 233 |
+
|
| 234 |
+
# Simulate training episodes
|
| 235 |
+
# TODO: Replace with actual training loop from agent/train.py
|
| 236 |
+
for ep in range(training_data["episodes"]):
|
| 237 |
+
await asyncio.sleep(2) # Simulate episode execution
|
| 238 |
+
|
| 239 |
+
platform = "Instagram" if ep % 2 == 0 else "Snapchat"
|
| 240 |
+
score = 0.85 + (ep * 0.001) # Simulated improving score
|
| 241 |
+
|
| 242 |
+
await websocket.send_json({
|
| 243 |
+
"type": "episode_complete",
|
| 244 |
+
"episode": ep,
|
| 245 |
+
"platform": platform,
|
| 246 |
+
"score": score,
|
| 247 |
+
"tp": 9,
|
| 248 |
+
"fp": 1,
|
| 249 |
+
"fn": 1,
|
| 250 |
+
})
|
| 251 |
+
|
| 252 |
+
await websocket.send_json({
|
| 253 |
+
"type": "training_complete",
|
| 254 |
+
"avg_score": 0.87,
|
| 255 |
+
})
|
| 256 |
+
|
| 257 |
+
except WebSocketDisconnect:
|
| 258 |
+
pass
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
# ============================================================================
|
| 262 |
+
# REST: Policy Endpoints
|
| 263 |
+
# ============================================================================
|
| 264 |
+
|
| 265 |
+
@app.post("/api/compile_policy")
|
| 266 |
+
async def compile_policy_rest(request: CompilePolicyRequest):
|
| 267 |
+
"""Compile policy (REST endpoint, non-streaming)."""
|
| 268 |
+
policy = get_policy(request.platform) if request.use_cache else compile_policy(request.platform, use_cache=False)
|
| 269 |
+
policy_cache[request.platform] = policy.model_dump()
|
| 270 |
+
|
| 271 |
+
return {
|
| 272 |
+
"policy": policy.model_dump(),
|
| 273 |
+
"sources": policy.sources,
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
@app.get("/api/policy/{platform}")
|
| 278 |
+
async def get_policy_rest(platform: str):
|
| 279 |
+
"""Get cached policy."""
|
| 280 |
+
if platform in policy_cache:
|
| 281 |
+
return {"policy": policy_cache[platform]}
|
| 282 |
+
|
| 283 |
+
policy = get_policy(platform)
|
| 284 |
+
policy_cache[platform] = policy.model_dump()
|
| 285 |
+
return {"policy": policy.model_dump()}
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
# ============================================================================
|
| 289 |
+
# REST: Episode Endpoints
|
| 290 |
+
# ============================================================================
|
| 291 |
+
|
| 292 |
+
@app.post("/api/episode/start")
|
| 293 |
+
async def start_episode(request: StartEpisodeRequest):
|
| 294 |
+
"""Start a new episode."""
|
| 295 |
+
episode_id = str(uuid.uuid4())
|
| 296 |
+
|
| 297 |
+
env = FakeGangEnvironment()
|
| 298 |
+
obs = env.reset(task=request.task, seed=request.seed)
|
| 299 |
+
|
| 300 |
+
# Load policy
|
| 301 |
+
policy = get_policy(obs.platform)
|
| 302 |
+
|
| 303 |
+
# Store episode state
|
| 304 |
+
active_episodes[episode_id] = {
|
| 305 |
+
"env": env,
|
| 306 |
+
"obs": obs,
|
| 307 |
+
"task": request.task,
|
| 308 |
+
"seed": request.seed,
|
| 309 |
+
"platform": obs.platform,
|
| 310 |
+
"policy": policy.model_dump(),
|
| 311 |
+
"created_at": datetime.now().isoformat(),
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
return {
|
| 315 |
+
"episode_id": episode_id,
|
| 316 |
+
"platform": obs.platform,
|
| 317 |
+
"observation": obs.model_dump(),
|
| 318 |
+
"policy": policy.model_dump(),
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
@app.post("/api/episode/{episode_id}/step")
|
| 323 |
+
async def step_episode(episode_id: str, request: StepActionRequest):
|
| 324 |
+
"""Execute one step in episode."""
|
| 325 |
+
if episode_id not in active_episodes:
|
| 326 |
+
raise HTTPException(status_code=404, detail="Episode not found")
|
| 327 |
+
|
| 328 |
+
ep_data = active_episodes[episode_id]
|
| 329 |
+
env = ep_data["env"]
|
| 330 |
+
|
| 331 |
+
# Create action
|
| 332 |
+
action = FakeGangAction(
|
| 333 |
+
action_type=ActionType[request.action_type.upper()],
|
| 334 |
+
account_id=request.account_id,
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
# Step environment
|
| 338 |
+
obs = env.step(action)
|
| 339 |
+
ep_data["obs"] = obs
|
| 340 |
+
|
| 341 |
+
# Build graph data
|
| 342 |
+
graph_data = {
|
| 343 |
+
"nodes": [
|
| 344 |
+
{
|
| 345 |
+
"id": p.account_id,
|
| 346 |
+
"fake_risk_score": p.fake_risk_score,
|
| 347 |
+
"hub_legitimacy_score": p.hub_legitimacy_score,
|
| 348 |
+
"is_fake": p.account_id in env._ep["gang_member_ids"],
|
| 349 |
+
}
|
| 350 |
+
for p in obs.visible_accounts
|
| 351 |
+
],
|
| 352 |
+
"edges": [
|
| 353 |
+
{
|
| 354 |
+
"source": acc_id,
|
| 355 |
+
"target": target_id,
|
| 356 |
+
"is_mutual": target_id in obs.graph_edges.get(target_id, []) and acc_id in obs.graph_edges[target_id],
|
| 357 |
+
}
|
| 358 |
+
for acc_id, targets in obs.graph_edges.items()
|
| 359 |
+
for target_id in targets
|
| 360 |
+
],
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
return {
|
| 364 |
+
"observation": obs.model_dump(),
|
| 365 |
+
"graph_data": graph_data,
|
| 366 |
+
"done": obs.done,
|
| 367 |
+
"reward": obs.reward,
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
@app.get("/api/episode/{episode_id}/state")
|
| 372 |
+
async def get_episode_state(episode_id: str):
|
| 373 |
+
"""Get current episode state."""
|
| 374 |
+
if episode_id not in active_episodes:
|
| 375 |
+
raise HTTPException(status_code=404, detail="Episode not found")
|
| 376 |
+
|
| 377 |
+
ep_data = active_episodes[episode_id]
|
| 378 |
+
obs = ep_data["obs"]
|
| 379 |
+
env = ep_data["env"]
|
| 380 |
+
|
| 381 |
+
# Build graph data
|
| 382 |
+
graph_data = {
|
| 383 |
+
"nodes": [
|
| 384 |
+
{
|
| 385 |
+
"id": p.account_id,
|
| 386 |
+
"fake_risk_score": p.fake_risk_score,
|
| 387 |
+
"hub_legitimacy_score": p.hub_legitimacy_score,
|
| 388 |
+
"is_fake": p.account_id in env._ep["gang_member_ids"],
|
| 389 |
+
}
|
| 390 |
+
for p in obs.visible_accounts
|
| 391 |
+
],
|
| 392 |
+
"edges": [
|
| 393 |
+
{
|
| 394 |
+
"source": acc_id,
|
| 395 |
+
"target": target_id,
|
| 396 |
+
"is_mutual": target_id in obs.graph_edges.get(target_id, []) and acc_id in obs.graph_edges[target_id],
|
| 397 |
+
}
|
| 398 |
+
for acc_id, targets in obs.graph_edges.items()
|
| 399 |
+
for target_id in targets
|
| 400 |
+
],
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
return {
|
| 404 |
+
"episode_id": episode_id,
|
| 405 |
+
"platform": ep_data["platform"],
|
| 406 |
+
"policy": ep_data["policy"],
|
| 407 |
+
"observation": obs.model_dump(),
|
| 408 |
+
"graph_data": graph_data,
|
| 409 |
+
"metrics": {
|
| 410 |
+
"steps_used": env._step_count,
|
| 411 |
+
"max_steps": env._max_steps,
|
| 412 |
+
"flagged_count": len(obs.flagged_ids),
|
| 413 |
+
"grader_score": env._last_grader_score,
|
| 414 |
+
},
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
|
| 418 |
+
# ============================================================================
|
| 419 |
+
# REST: Training Endpoints
|
| 420 |
+
# ============================================================================
|
| 421 |
+
|
| 422 |
+
@app.post("/api/training/start")
|
| 423 |
+
async def start_training(request: StartTrainingRequest):
|
| 424 |
+
"""Start training loop."""
|
| 425 |
+
training_id = str(uuid.uuid4())
|
| 426 |
+
|
| 427 |
+
active_trainings[training_id] = {
|
| 428 |
+
"status": "running",
|
| 429 |
+
"episodes": request.episodes,
|
| 430 |
+
"task": request.task,
|
| 431 |
+
"platforms": request.platforms or ["Instagram", "Snapchat"],
|
| 432 |
+
"results": [],
|
| 433 |
+
"created_at": datetime.now().isoformat(),
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
return {
|
| 437 |
+
"training_id": training_id,
|
| 438 |
+
"status": "running",
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
@app.get("/api/training/{training_id}/metrics")
|
| 443 |
+
async def get_training_metrics(training_id: str):
|
| 444 |
+
"""Get training metrics."""
|
| 445 |
+
if training_id not in active_trainings:
|
| 446 |
+
raise HTTPException(status_code=404, detail="Training not found")
|
| 447 |
+
|
| 448 |
+
return {
|
| 449 |
+
"training_id": training_id,
|
| 450 |
+
"metrics": active_trainings[training_id].get("results", []),
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
# ============================================================================
|
| 455 |
+
# Health Check
|
| 456 |
+
# ============================================================================
|
| 457 |
+
|
| 458 |
+
@app.get("/health")
|
| 459 |
+
async def health_check():
|
| 460 |
+
"""Health check endpoint."""
|
| 461 |
+
return {
|
| 462 |
+
"status": "healthy",
|
| 463 |
+
"active_episodes": len(active_episodes),
|
| 464 |
+
"active_trainings": len(active_trainings),
|
| 465 |
+
"timestamp": datetime.now().isoformat(),
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
if __name__ == "__main__":
|
| 472 |
+
import uvicorn
|
| 473 |
+
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|
dashboard/backend/requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.109.0
|
| 2 |
+
uvicorn[standard]==0.27.0
|
| 3 |
+
websockets==12.0
|
| 4 |
+
python-multipart==0.0.6
|
| 5 |
+
pydantic==2.5.3
|
dashboard/frontend/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title>Fake Gang Detection Dashboard</title>
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div id="root"></div>
|
| 11 |
+
<script type="module" src="/src/main.tsx"></script>
|
| 12 |
+
</body>
|
| 13 |
+
</html>
|
dashboard/frontend/node_modules/@alloc/quick-lru/index.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
declare namespace QuickLRU {
|
| 2 |
+
interface Options<KeyType, ValueType> {
|
| 3 |
+
/**
|
| 4 |
+
The maximum number of milliseconds an item should remain in the cache.
|
| 5 |
+
|
| 6 |
+
@default Infinity
|
| 7 |
+
|
| 8 |
+
By default, `maxAge` will be `Infinity`, which means that items will never expire.
|
| 9 |
+
Lazy expiration upon the next write or read call.
|
| 10 |
+
|
| 11 |
+
Individual expiration of an item can be specified by the `set(key, value, maxAge)` method.
|
| 12 |
+
*/
|
| 13 |
+
readonly maxAge?: number;
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
The maximum number of items before evicting the least recently used items.
|
| 17 |
+
*/
|
| 18 |
+
readonly maxSize: number;
|
| 19 |
+
|
| 20 |
+
/**
|
| 21 |
+
Called right before an item is evicted from the cache.
|
| 22 |
+
|
| 23 |
+
Useful for side effects or for items like object URLs that need explicit cleanup (`revokeObjectURL`).
|
| 24 |
+
*/
|
| 25 |
+
onEviction?: (key: KeyType, value: ValueType) => void;
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
declare class QuickLRU<KeyType, ValueType>
|
| 30 |
+
implements Iterable<[KeyType, ValueType]> {
|
| 31 |
+
/**
|
| 32 |
+
The stored item count.
|
| 33 |
+
*/
|
| 34 |
+
readonly size: number;
|
| 35 |
+
|
| 36 |
+
/**
|
| 37 |
+
Simple ["Least Recently Used" (LRU) cache](https://en.m.wikipedia.org/wiki/Cache_replacement_policies#Least_Recently_Used_.28LRU.29).
|
| 38 |
+
|
| 39 |
+
The instance is [`iterable`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols) so you can use it directly in a [`for…of`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/for...of) loop.
|
| 40 |
+
|
| 41 |
+
@example
|
| 42 |
+
```
|
| 43 |
+
import QuickLRU = require('quick-lru');
|
| 44 |
+
|
| 45 |
+
const lru = new QuickLRU({maxSize: 1000});
|
| 46 |
+
|
| 47 |
+
lru.set('🦄', '🌈');
|
| 48 |
+
|
| 49 |
+
lru.has('🦄');
|
| 50 |
+
//=> true
|
| 51 |
+
|
| 52 |
+
lru.get('🦄');
|
| 53 |
+
//=> '🌈'
|
| 54 |
+
```
|
| 55 |
+
*/
|
| 56 |
+
constructor(options: QuickLRU.Options<KeyType, ValueType>);
|
| 57 |
+
|
| 58 |
+
[Symbol.iterator](): IterableIterator<[KeyType, ValueType]>;
|
| 59 |
+
|
| 60 |
+
/**
|
| 61 |
+
Set an item. Returns the instance.
|
| 62 |
+
|
| 63 |
+
Individual expiration of an item can be specified with the `maxAge` option. If not specified, the global `maxAge` value will be used in case it is specified in the constructor, otherwise the item will never expire.
|
| 64 |
+
|
| 65 |
+
@returns The list instance.
|
| 66 |
+
*/
|
| 67 |
+
set(key: KeyType, value: ValueType, options?: {maxAge?: number}): this;
|
| 68 |
+
|
| 69 |
+
/**
|
| 70 |
+
Get an item.
|
| 71 |
+
|
| 72 |
+
@returns The stored item or `undefined`.
|
| 73 |
+
*/
|
| 74 |
+
get(key: KeyType): ValueType | undefined;
|
| 75 |
+
|
| 76 |
+
/**
|
| 77 |
+
Check if an item exists.
|
| 78 |
+
*/
|
| 79 |
+
has(key: KeyType): boolean;
|
| 80 |
+
|
| 81 |
+
/**
|
| 82 |
+
Get an item without marking it as recently used.
|
| 83 |
+
|
| 84 |
+
@returns The stored item or `undefined`.
|
| 85 |
+
*/
|
| 86 |
+
peek(key: KeyType): ValueType | undefined;
|
| 87 |
+
|
| 88 |
+
/**
|
| 89 |
+
Delete an item.
|
| 90 |
+
|
| 91 |
+
@returns `true` if the item is removed or `false` if the item doesn't exist.
|
| 92 |
+
*/
|
| 93 |
+
delete(key: KeyType): boolean;
|
| 94 |
+
|
| 95 |
+
/**
|
| 96 |
+
Delete all items.
|
| 97 |
+
*/
|
| 98 |
+
clear(): void;
|
| 99 |
+
|
| 100 |
+
/**
|
| 101 |
+
Update the `maxSize` in-place, discarding items as necessary. Insertion order is mostly preserved, though this is not a strong guarantee.
|
| 102 |
+
|
| 103 |
+
Useful for on-the-fly tuning of cache sizes in live systems.
|
| 104 |
+
*/
|
| 105 |
+
resize(maxSize: number): void;
|
| 106 |
+
|
| 107 |
+
/**
|
| 108 |
+
Iterable for all the keys.
|
| 109 |
+
*/
|
| 110 |
+
keys(): IterableIterator<KeyType>;
|
| 111 |
+
|
| 112 |
+
/**
|
| 113 |
+
Iterable for all the values.
|
| 114 |
+
*/
|
| 115 |
+
values(): IterableIterator<ValueType>;
|
| 116 |
+
|
| 117 |
+
/**
|
| 118 |
+
Iterable for all entries, starting with the oldest (ascending in recency).
|
| 119 |
+
*/
|
| 120 |
+
entriesAscending(): IterableIterator<[KeyType, ValueType]>;
|
| 121 |
+
|
| 122 |
+
/**
|
| 123 |
+
Iterable for all entries, starting with the newest (descending in recency).
|
| 124 |
+
*/
|
| 125 |
+
entriesDescending(): IterableIterator<[KeyType, ValueType]>;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
export = QuickLRU;
|
dashboard/frontend/node_modules/@alloc/quick-lru/index.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use strict';
|
| 2 |
+
|
| 3 |
+
class QuickLRU {
|
| 4 |
+
constructor(options = {}) {
|
| 5 |
+
if (!(options.maxSize && options.maxSize > 0)) {
|
| 6 |
+
throw new TypeError('`maxSize` must be a number greater than 0');
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
if (typeof options.maxAge === 'number' && options.maxAge === 0) {
|
| 10 |
+
throw new TypeError('`maxAge` must be a number greater than 0');
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
this.maxSize = options.maxSize;
|
| 14 |
+
this.maxAge = options.maxAge || Infinity;
|
| 15 |
+
this.onEviction = options.onEviction;
|
| 16 |
+
this.cache = new Map();
|
| 17 |
+
this.oldCache = new Map();
|
| 18 |
+
this._size = 0;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
_emitEvictions(cache) {
|
| 22 |
+
if (typeof this.onEviction !== 'function') {
|
| 23 |
+
return;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
for (const [key, item] of cache) {
|
| 27 |
+
this.onEviction(key, item.value);
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
_deleteIfExpired(key, item) {
|
| 32 |
+
if (typeof item.expiry === 'number' && item.expiry <= Date.now()) {
|
| 33 |
+
if (typeof this.onEviction === 'function') {
|
| 34 |
+
this.onEviction(key, item.value);
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
return this.delete(key);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
return false;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
_getOrDeleteIfExpired(key, item) {
|
| 44 |
+
const deleted = this._deleteIfExpired(key, item);
|
| 45 |
+
if (deleted === false) {
|
| 46 |
+
return item.value;
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
_getItemValue(key, item) {
|
| 51 |
+
return item.expiry ? this._getOrDeleteIfExpired(key, item) : item.value;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
_peek(key, cache) {
|
| 55 |
+
const item = cache.get(key);
|
| 56 |
+
|
| 57 |
+
return this._getItemValue(key, item);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
_set(key, value) {
|
| 61 |
+
this.cache.set(key, value);
|
| 62 |
+
this._size++;
|
| 63 |
+
|
| 64 |
+
if (this._size >= this.maxSize) {
|
| 65 |
+
this._size = 0;
|
| 66 |
+
this._emitEvictions(this.oldCache);
|
| 67 |
+
this.oldCache = this.cache;
|
| 68 |
+
this.cache = new Map();
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
_moveToRecent(key, item) {
|
| 73 |
+
this.oldCache.delete(key);
|
| 74 |
+
this._set(key, item);
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
* _entriesAscending() {
|
| 78 |
+
for (const item of this.oldCache) {
|
| 79 |
+
const [key, value] = item;
|
| 80 |
+
if (!this.cache.has(key)) {
|
| 81 |
+
const deleted = this._deleteIfExpired(key, value);
|
| 82 |
+
if (deleted === false) {
|
| 83 |
+
yield item;
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
for (const item of this.cache) {
|
| 89 |
+
const [key, value] = item;
|
| 90 |
+
const deleted = this._deleteIfExpired(key, value);
|
| 91 |
+
if (deleted === false) {
|
| 92 |
+
yield item;
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
get(key) {
|
| 98 |
+
if (this.cache.has(key)) {
|
| 99 |
+
const item = this.cache.get(key);
|
| 100 |
+
|
| 101 |
+
return this._getItemValue(key, item);
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
if (this.oldCache.has(key)) {
|
| 105 |
+
const item = this.oldCache.get(key);
|
| 106 |
+
if (this._deleteIfExpired(key, item) === false) {
|
| 107 |
+
this._moveToRecent(key, item);
|
| 108 |
+
return item.value;
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
set(key, value, {maxAge = this.maxAge === Infinity ? undefined : Date.now() + this.maxAge} = {}) {
|
| 114 |
+
if (this.cache.has(key)) {
|
| 115 |
+
this.cache.set(key, {
|
| 116 |
+
value,
|
| 117 |
+
maxAge
|
| 118 |
+
});
|
| 119 |
+
} else {
|
| 120 |
+
this._set(key, {value, expiry: maxAge});
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
has(key) {
|
| 125 |
+
if (this.cache.has(key)) {
|
| 126 |
+
return !this._deleteIfExpired(key, this.cache.get(key));
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
if (this.oldCache.has(key)) {
|
| 130 |
+
return !this._deleteIfExpired(key, this.oldCache.get(key));
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
return false;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
peek(key) {
|
| 137 |
+
if (this.cache.has(key)) {
|
| 138 |
+
return this._peek(key, this.cache);
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
if (this.oldCache.has(key)) {
|
| 142 |
+
return this._peek(key, this.oldCache);
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
delete(key) {
|
| 147 |
+
const deleted = this.cache.delete(key);
|
| 148 |
+
if (deleted) {
|
| 149 |
+
this._size--;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
return this.oldCache.delete(key) || deleted;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
clear() {
|
| 156 |
+
this.cache.clear();
|
| 157 |
+
this.oldCache.clear();
|
| 158 |
+
this._size = 0;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
resize(newSize) {
|
| 162 |
+
if (!(newSize && newSize > 0)) {
|
| 163 |
+
throw new TypeError('`maxSize` must be a number greater than 0');
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
const items = [...this._entriesAscending()];
|
| 167 |
+
const removeCount = items.length - newSize;
|
| 168 |
+
if (removeCount < 0) {
|
| 169 |
+
this.cache = new Map(items);
|
| 170 |
+
this.oldCache = new Map();
|
| 171 |
+
this._size = items.length;
|
| 172 |
+
} else {
|
| 173 |
+
if (removeCount > 0) {
|
| 174 |
+
this._emitEvictions(items.slice(0, removeCount));
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
this.oldCache = new Map(items.slice(removeCount));
|
| 178 |
+
this.cache = new Map();
|
| 179 |
+
this._size = 0;
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
this.maxSize = newSize;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
* keys() {
|
| 186 |
+
for (const [key] of this) {
|
| 187 |
+
yield key;
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
* values() {
|
| 192 |
+
for (const [, value] of this) {
|
| 193 |
+
yield value;
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
* [Symbol.iterator]() {
|
| 198 |
+
for (const item of this.cache) {
|
| 199 |
+
const [key, value] = item;
|
| 200 |
+
const deleted = this._deleteIfExpired(key, value);
|
| 201 |
+
if (deleted === false) {
|
| 202 |
+
yield [key, value.value];
|
| 203 |
+
}
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
for (const item of this.oldCache) {
|
| 207 |
+
const [key, value] = item;
|
| 208 |
+
if (!this.cache.has(key)) {
|
| 209 |
+
const deleted = this._deleteIfExpired(key, value);
|
| 210 |
+
if (deleted === false) {
|
| 211 |
+
yield [key, value.value];
|
| 212 |
+
}
|
| 213 |
+
}
|
| 214 |
+
}
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
* entriesDescending() {
|
| 218 |
+
let items = [...this.cache];
|
| 219 |
+
for (let i = items.length - 1; i >= 0; --i) {
|
| 220 |
+
const item = items[i];
|
| 221 |
+
const [key, value] = item;
|
| 222 |
+
const deleted = this._deleteIfExpired(key, value);
|
| 223 |
+
if (deleted === false) {
|
| 224 |
+
yield [key, value.value];
|
| 225 |
+
}
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
items = [...this.oldCache];
|
| 229 |
+
for (let i = items.length - 1; i >= 0; --i) {
|
| 230 |
+
const item = items[i];
|
| 231 |
+
const [key, value] = item;
|
| 232 |
+
if (!this.cache.has(key)) {
|
| 233 |
+
const deleted = this._deleteIfExpired(key, value);
|
| 234 |
+
if (deleted === false) {
|
| 235 |
+
yield [key, value.value];
|
| 236 |
+
}
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
* entriesAscending() {
|
| 242 |
+
for (const [key, value] of this._entriesAscending()) {
|
| 243 |
+
yield [key, value.value];
|
| 244 |
+
}
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
get size() {
|
| 248 |
+
if (!this._size) {
|
| 249 |
+
return this.oldCache.size;
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
let oldCacheSize = 0;
|
| 253 |
+
for (const key of this.oldCache.keys()) {
|
| 254 |
+
if (!this.cache.has(key)) {
|
| 255 |
+
oldCacheSize++;
|
| 256 |
+
}
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
return Math.min(this._size + oldCacheSize, this.maxSize);
|
| 260 |
+
}
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
module.exports = QuickLRU;
|
dashboard/frontend/node_modules/@alloc/quick-lru/license
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
| 6 |
+
|
| 7 |
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
| 8 |
+
|
| 9 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
dashboard/frontend/node_modules/@alloc/quick-lru/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "@alloc/quick-lru",
|
| 3 |
+
"version": "5.2.0",
|
| 4 |
+
"description": "Simple “Least Recently Used” (LRU) cache",
|
| 5 |
+
"license": "MIT",
|
| 6 |
+
"repository": "sindresorhus/quick-lru",
|
| 7 |
+
"funding": "https://github.com/sponsors/sindresorhus",
|
| 8 |
+
"author": {
|
| 9 |
+
"name": "Sindre Sorhus",
|
| 10 |
+
"email": "sindresorhus@gmail.com",
|
| 11 |
+
"url": "https://sindresorhus.com"
|
| 12 |
+
},
|
| 13 |
+
"engines": {
|
| 14 |
+
"node": ">=10"
|
| 15 |
+
},
|
| 16 |
+
"scripts": {
|
| 17 |
+
"test": "xo && nyc ava && tsd"
|
| 18 |
+
},
|
| 19 |
+
"files": [
|
| 20 |
+
"index.js",
|
| 21 |
+
"index.d.ts"
|
| 22 |
+
],
|
| 23 |
+
"keywords": [
|
| 24 |
+
"lru",
|
| 25 |
+
"quick",
|
| 26 |
+
"cache",
|
| 27 |
+
"caching",
|
| 28 |
+
"least",
|
| 29 |
+
"recently",
|
| 30 |
+
"used",
|
| 31 |
+
"fast",
|
| 32 |
+
"map",
|
| 33 |
+
"hash",
|
| 34 |
+
"buffer"
|
| 35 |
+
],
|
| 36 |
+
"devDependencies": {
|
| 37 |
+
"ava": "^2.0.0",
|
| 38 |
+
"coveralls": "^3.0.3",
|
| 39 |
+
"nyc": "^15.0.0",
|
| 40 |
+
"tsd": "^0.11.0",
|
| 41 |
+
"xo": "^0.26.0"
|
| 42 |
+
}
|
| 43 |
+
}
|
dashboard/frontend/node_modules/@alloc/quick-lru/readme.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# quick-lru [](https://travis-ci.org/sindresorhus/quick-lru) [](https://coveralls.io/github/sindresorhus/quick-lru?branch=master)
|
| 2 |
+
|
| 3 |
+
> Simple [“Least Recently Used” (LRU) cache](https://en.m.wikipedia.org/wiki/Cache_replacement_policies#Least_Recently_Used_.28LRU.29)
|
| 4 |
+
|
| 5 |
+
Useful when you need to cache something and limit memory usage.
|
| 6 |
+
|
| 7 |
+
Inspired by the [`hashlru` algorithm](https://github.com/dominictarr/hashlru#algorithm), but instead uses [`Map`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map) to support keys of any type, not just strings, and values can be `undefined`.
|
| 8 |
+
|
| 9 |
+
## Install
|
| 10 |
+
|
| 11 |
+
```
|
| 12 |
+
$ npm install quick-lru
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
## Usage
|
| 16 |
+
|
| 17 |
+
```js
|
| 18 |
+
const QuickLRU = require('quick-lru');
|
| 19 |
+
|
| 20 |
+
const lru = new QuickLRU({maxSize: 1000});
|
| 21 |
+
|
| 22 |
+
lru.set('🦄', '🌈');
|
| 23 |
+
|
| 24 |
+
lru.has('🦄');
|
| 25 |
+
//=> true
|
| 26 |
+
|
| 27 |
+
lru.get('🦄');
|
| 28 |
+
//=> '🌈'
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
## API
|
| 32 |
+
|
| 33 |
+
### new QuickLRU(options?)
|
| 34 |
+
|
| 35 |
+
Returns a new instance.
|
| 36 |
+
|
| 37 |
+
### options
|
| 38 |
+
|
| 39 |
+
Type: `object`
|
| 40 |
+
|
| 41 |
+
#### maxSize
|
| 42 |
+
|
| 43 |
+
*Required*\
|
| 44 |
+
Type: `number`
|
| 45 |
+
|
| 46 |
+
The maximum number of items before evicting the least recently used items.
|
| 47 |
+
|
| 48 |
+
#### maxAge
|
| 49 |
+
|
| 50 |
+
Type: `number`\
|
| 51 |
+
Default: `Infinity`
|
| 52 |
+
|
| 53 |
+
The maximum number of milliseconds an item should remain in cache.
|
| 54 |
+
By default maxAge will be Infinity, which means that items will never expire.
|
| 55 |
+
|
| 56 |
+
Lazy expiration happens upon the next `write` or `read` call.
|
| 57 |
+
|
| 58 |
+
Individual expiration of an item can be specified by the `set(key, value, options)` method.
|
| 59 |
+
|
| 60 |
+
#### onEviction
|
| 61 |
+
|
| 62 |
+
*Optional*\
|
| 63 |
+
Type: `(key, value) => void`
|
| 64 |
+
|
| 65 |
+
Called right before an item is evicted from the cache.
|
| 66 |
+
|
| 67 |
+
Useful for side effects or for items like object URLs that need explicit cleanup (`revokeObjectURL`).
|
| 68 |
+
|
| 69 |
+
### Instance
|
| 70 |
+
|
| 71 |
+
The instance is [`iterable`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols) so you can use it directly in a [`for…of`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/for...of) loop.
|
| 72 |
+
|
| 73 |
+
Both `key` and `value` can be of any type.
|
| 74 |
+
|
| 75 |
+
#### .set(key, value, options?)
|
| 76 |
+
|
| 77 |
+
Set an item. Returns the instance.
|
| 78 |
+
|
| 79 |
+
Individual expiration of an item can be specified with the `maxAge` option. If not specified, the global `maxAge` value will be used in case it is specified on the constructor, otherwise the item will never expire.
|
| 80 |
+
|
| 81 |
+
#### .get(key)
|
| 82 |
+
|
| 83 |
+
Get an item.
|
| 84 |
+
|
| 85 |
+
#### .has(key)
|
| 86 |
+
|
| 87 |
+
Check if an item exists.
|
| 88 |
+
|
| 89 |
+
#### .peek(key)
|
| 90 |
+
|
| 91 |
+
Get an item without marking it as recently used.
|
| 92 |
+
|
| 93 |
+
#### .delete(key)
|
| 94 |
+
|
| 95 |
+
Delete an item.
|
| 96 |
+
|
| 97 |
+
Returns `true` if the item is removed or `false` if the item doesn't exist.
|
| 98 |
+
|
| 99 |
+
#### .clear()
|
| 100 |
+
|
| 101 |
+
Delete all items.
|
| 102 |
+
|
| 103 |
+
#### .resize(maxSize)
|
| 104 |
+
|
| 105 |
+
Update the `maxSize`, discarding items as necessary. Insertion order is mostly preserved, though this is not a strong guarantee.
|
| 106 |
+
|
| 107 |
+
Useful for on-the-fly tuning of cache sizes in live systems.
|
| 108 |
+
|
| 109 |
+
#### .keys()
|
| 110 |
+
|
| 111 |
+
Iterable for all the keys.
|
| 112 |
+
|
| 113 |
+
#### .values()
|
| 114 |
+
|
| 115 |
+
Iterable for all the values.
|
| 116 |
+
|
| 117 |
+
#### .entriesAscending()
|
| 118 |
+
|
| 119 |
+
Iterable for all entries, starting with the oldest (ascending in recency).
|
| 120 |
+
|
| 121 |
+
#### .entriesDescending()
|
| 122 |
+
|
| 123 |
+
Iterable for all entries, starting with the newest (descending in recency).
|
| 124 |
+
|
| 125 |
+
#### .size
|
| 126 |
+
|
| 127 |
+
The stored item count.
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
<div align="center">
|
| 132 |
+
<b>
|
| 133 |
+
<a href="https://tidelift.com/subscription/pkg/npm-quick-lru?utm_source=npm-quick-lru&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
|
| 134 |
+
</b>
|
| 135 |
+
<br>
|
| 136 |
+
<sub>
|
| 137 |
+
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
|
| 138 |
+
</sub>
|
| 139 |
+
</div>
|
dashboard/frontend/node_modules/@babel/code-frame/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2014-present Sebastian McKenzie and other contributors
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining
|
| 6 |
+
a copy of this software and associated documentation files (the
|
| 7 |
+
"Software"), to deal in the Software without restriction, including
|
| 8 |
+
without limitation the rights to use, copy, modify, merge, publish,
|
| 9 |
+
distribute, sublicense, and/or sell copies of the Software, and to
|
| 10 |
+
permit persons to whom the Software is furnished to do so, subject to
|
| 11 |
+
the following conditions:
|
| 12 |
+
|
| 13 |
+
The above copyright notice and this permission notice shall be
|
| 14 |
+
included in all copies or substantial portions of the Software.
|
| 15 |
+
|
| 16 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 17 |
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 18 |
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 19 |
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 20 |
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 21 |
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 22 |
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
dashboard/frontend/node_modules/@babel/code-frame/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @babel/code-frame
|
| 2 |
+
|
| 3 |
+
> Generate errors that contain a code frame that point to source locations.
|
| 4 |
+
|
| 5 |
+
See our website [@babel/code-frame](https://babeljs.io/docs/babel-code-frame) for more information.
|
| 6 |
+
|
| 7 |
+
## Install
|
| 8 |
+
|
| 9 |
+
Using npm:
|
| 10 |
+
|
| 11 |
+
```sh
|
| 12 |
+
npm install --save-dev @babel/code-frame
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
or using yarn:
|
| 16 |
+
|
| 17 |
+
```sh
|
| 18 |
+
yarn add @babel/code-frame --dev
|
| 19 |
+
```
|
dashboard/frontend/node_modules/@babel/code-frame/lib/index.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use strict';
|
| 2 |
+
|
| 3 |
+
Object.defineProperty(exports, '__esModule', { value: true });
|
| 4 |
+
|
| 5 |
+
var picocolors = require('picocolors');
|
| 6 |
+
var jsTokens = require('js-tokens');
|
| 7 |
+
var helperValidatorIdentifier = require('@babel/helper-validator-identifier');
|
| 8 |
+
|
| 9 |
+
function isColorSupported() {
|
| 10 |
+
return (typeof process === "object" && (process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false") ? false : picocolors.isColorSupported
|
| 11 |
+
);
|
| 12 |
+
}
|
| 13 |
+
const compose = (f, g) => v => f(g(v));
|
| 14 |
+
function buildDefs(colors) {
|
| 15 |
+
return {
|
| 16 |
+
keyword: colors.cyan,
|
| 17 |
+
capitalized: colors.yellow,
|
| 18 |
+
jsxIdentifier: colors.yellow,
|
| 19 |
+
punctuator: colors.yellow,
|
| 20 |
+
number: colors.magenta,
|
| 21 |
+
string: colors.green,
|
| 22 |
+
regex: colors.magenta,
|
| 23 |
+
comment: colors.gray,
|
| 24 |
+
invalid: compose(compose(colors.white, colors.bgRed), colors.bold),
|
| 25 |
+
gutter: colors.gray,
|
| 26 |
+
marker: compose(colors.red, colors.bold),
|
| 27 |
+
message: compose(colors.red, colors.bold),
|
| 28 |
+
reset: colors.reset
|
| 29 |
+
};
|
| 30 |
+
}
|
| 31 |
+
const defsOn = buildDefs(picocolors.createColors(true));
|
| 32 |
+
const defsOff = buildDefs(picocolors.createColors(false));
|
| 33 |
+
function getDefs(enabled) {
|
| 34 |
+
return enabled ? defsOn : defsOff;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
const sometimesKeywords = new Set(["as", "async", "from", "get", "of", "set"]);
|
| 38 |
+
const NEWLINE$1 = /\r\n|[\n\r\u2028\u2029]/;
|
| 39 |
+
const BRACKET = /^[()[\]{}]$/;
|
| 40 |
+
let tokenize;
|
| 41 |
+
const JSX_TAG = /^[a-z][\w-]*$/i;
|
| 42 |
+
const getTokenType = function (token, offset, text) {
|
| 43 |
+
if (token.type === "name") {
|
| 44 |
+
const tokenValue = token.value;
|
| 45 |
+
if (helperValidatorIdentifier.isKeyword(tokenValue) || helperValidatorIdentifier.isStrictReservedWord(tokenValue, true) || sometimesKeywords.has(tokenValue)) {
|
| 46 |
+
return "keyword";
|
| 47 |
+
}
|
| 48 |
+
if (JSX_TAG.test(tokenValue) && (text[offset - 1] === "<" || text.slice(offset - 2, offset) === "</")) {
|
| 49 |
+
return "jsxIdentifier";
|
| 50 |
+
}
|
| 51 |
+
const firstChar = String.fromCodePoint(tokenValue.codePointAt(0));
|
| 52 |
+
if (firstChar !== firstChar.toLowerCase()) {
|
| 53 |
+
return "capitalized";
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
if (token.type === "punctuator" && BRACKET.test(token.value)) {
|
| 57 |
+
return "bracket";
|
| 58 |
+
}
|
| 59 |
+
if (token.type === "invalid" && (token.value === "@" || token.value === "#")) {
|
| 60 |
+
return "punctuator";
|
| 61 |
+
}
|
| 62 |
+
return token.type;
|
| 63 |
+
};
|
| 64 |
+
tokenize = function* (text) {
|
| 65 |
+
let match;
|
| 66 |
+
while (match = jsTokens.default.exec(text)) {
|
| 67 |
+
const token = jsTokens.matchToToken(match);
|
| 68 |
+
yield {
|
| 69 |
+
type: getTokenType(token, match.index, text),
|
| 70 |
+
value: token.value
|
| 71 |
+
};
|
| 72 |
+
}
|
| 73 |
+
};
|
| 74 |
+
function highlight(text) {
|
| 75 |
+
if (text === "") return "";
|
| 76 |
+
const defs = getDefs(true);
|
| 77 |
+
let highlighted = "";
|
| 78 |
+
for (const {
|
| 79 |
+
type,
|
| 80 |
+
value
|
| 81 |
+
} of tokenize(text)) {
|
| 82 |
+
if (type in defs) {
|
| 83 |
+
highlighted += value.split(NEWLINE$1).map(str => defs[type](str)).join("\n");
|
| 84 |
+
} else {
|
| 85 |
+
highlighted += value;
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
return highlighted;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
let deprecationWarningShown = false;
|
| 92 |
+
const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
|
| 93 |
+
function getMarkerLines(loc, source, opts, startLineBaseZero) {
|
| 94 |
+
const startLoc = Object.assign({
|
| 95 |
+
column: 0,
|
| 96 |
+
line: -1
|
| 97 |
+
}, loc.start);
|
| 98 |
+
const endLoc = Object.assign({}, startLoc, loc.end);
|
| 99 |
+
const {
|
| 100 |
+
linesAbove = 2,
|
| 101 |
+
linesBelow = 3
|
| 102 |
+
} = opts || {};
|
| 103 |
+
const startLine = startLoc.line - startLineBaseZero;
|
| 104 |
+
const startColumn = startLoc.column;
|
| 105 |
+
const endLine = endLoc.line - startLineBaseZero;
|
| 106 |
+
const endColumn = endLoc.column;
|
| 107 |
+
let start = Math.max(startLine - (linesAbove + 1), 0);
|
| 108 |
+
let end = Math.min(source.length, endLine + linesBelow);
|
| 109 |
+
if (startLine === -1) {
|
| 110 |
+
start = 0;
|
| 111 |
+
}
|
| 112 |
+
if (endLine === -1) {
|
| 113 |
+
end = source.length;
|
| 114 |
+
}
|
| 115 |
+
const lineDiff = endLine - startLine;
|
| 116 |
+
const markerLines = {};
|
| 117 |
+
if (lineDiff) {
|
| 118 |
+
for (let i = 0; i <= lineDiff; i++) {
|
| 119 |
+
const lineNumber = i + startLine;
|
| 120 |
+
if (!startColumn) {
|
| 121 |
+
markerLines[lineNumber] = true;
|
| 122 |
+
} else if (i === 0) {
|
| 123 |
+
const sourceLength = source[lineNumber - 1].length;
|
| 124 |
+
markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1];
|
| 125 |
+
} else if (i === lineDiff) {
|
| 126 |
+
markerLines[lineNumber] = [0, endColumn];
|
| 127 |
+
} else {
|
| 128 |
+
const sourceLength = source[lineNumber - i].length;
|
| 129 |
+
markerLines[lineNumber] = [0, sourceLength];
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
} else {
|
| 133 |
+
if (startColumn === endColumn) {
|
| 134 |
+
if (startColumn) {
|
| 135 |
+
markerLines[startLine] = [startColumn, 0];
|
| 136 |
+
} else {
|
| 137 |
+
markerLines[startLine] = true;
|
| 138 |
+
}
|
| 139 |
+
} else {
|
| 140 |
+
markerLines[startLine] = [startColumn, endColumn - startColumn];
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
return {
|
| 144 |
+
start,
|
| 145 |
+
end,
|
| 146 |
+
markerLines
|
| 147 |
+
};
|
| 148 |
+
}
|
| 149 |
+
function codeFrameColumns(rawLines, loc, opts = {}) {
|
| 150 |
+
const shouldHighlight = opts.forceColor || isColorSupported() && opts.highlightCode;
|
| 151 |
+
const startLineBaseZero = (opts.startLine || 1) - 1;
|
| 152 |
+
const defs = getDefs(shouldHighlight);
|
| 153 |
+
const lines = rawLines.split(NEWLINE);
|
| 154 |
+
const {
|
| 155 |
+
start,
|
| 156 |
+
end,
|
| 157 |
+
markerLines
|
| 158 |
+
} = getMarkerLines(loc, lines, opts, startLineBaseZero);
|
| 159 |
+
const hasColumns = loc.start && typeof loc.start.column === "number";
|
| 160 |
+
const numberMaxWidth = String(end + startLineBaseZero).length;
|
| 161 |
+
const highlightedLines = shouldHighlight ? highlight(rawLines) : rawLines;
|
| 162 |
+
let frame = highlightedLines.split(NEWLINE, end).slice(start, end).map((line, index) => {
|
| 163 |
+
const number = start + 1 + index;
|
| 164 |
+
const paddedNumber = ` ${number + startLineBaseZero}`.slice(-numberMaxWidth);
|
| 165 |
+
const gutter = ` ${paddedNumber} |`;
|
| 166 |
+
const hasMarker = markerLines[number];
|
| 167 |
+
const lastMarkerLine = !markerLines[number + 1];
|
| 168 |
+
if (hasMarker) {
|
| 169 |
+
let markerLine = "";
|
| 170 |
+
if (Array.isArray(hasMarker)) {
|
| 171 |
+
const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
|
| 172 |
+
const numberOfMarkers = hasMarker[1] || 1;
|
| 173 |
+
markerLine = ["\n ", defs.gutter(gutter.replace(/\d/g, " ")), " ", markerSpacing, defs.marker("^").repeat(numberOfMarkers)].join("");
|
| 174 |
+
if (lastMarkerLine && opts.message) {
|
| 175 |
+
markerLine += " " + defs.message(opts.message);
|
| 176 |
+
}
|
| 177 |
+
}
|
| 178 |
+
return [defs.marker(">"), defs.gutter(gutter), line.length > 0 ? ` ${line}` : "", markerLine].join("");
|
| 179 |
+
} else {
|
| 180 |
+
return ` ${defs.gutter(gutter)}${line.length > 0 ? ` ${line}` : ""}`;
|
| 181 |
+
}
|
| 182 |
+
}).join("\n");
|
| 183 |
+
if (opts.message && !hasColumns) {
|
| 184 |
+
frame = `${" ".repeat(numberMaxWidth + 1)}${opts.message}\n${frame}`;
|
| 185 |
+
}
|
| 186 |
+
if (shouldHighlight) {
|
| 187 |
+
return defs.reset(frame);
|
| 188 |
+
} else {
|
| 189 |
+
return frame;
|
| 190 |
+
}
|
| 191 |
+
}
|
| 192 |
+
function index (rawLines, lineNumber, colNumber, opts = {}) {
|
| 193 |
+
if (!deprecationWarningShown) {
|
| 194 |
+
deprecationWarningShown = true;
|
| 195 |
+
const message = "Passing lineNumber and colNumber is deprecated to @babel/code-frame. Please use `codeFrameColumns`.";
|
| 196 |
+
if (process.emitWarning) {
|
| 197 |
+
process.emitWarning(message, "DeprecationWarning");
|
| 198 |
+
} else {
|
| 199 |
+
const deprecationError = new Error(message);
|
| 200 |
+
deprecationError.name = "DeprecationWarning";
|
| 201 |
+
console.warn(new Error(message));
|
| 202 |
+
}
|
| 203 |
+
}
|
| 204 |
+
colNumber = Math.max(colNumber, 0);
|
| 205 |
+
const location = {
|
| 206 |
+
start: {
|
| 207 |
+
column: colNumber,
|
| 208 |
+
line: lineNumber
|
| 209 |
+
}
|
| 210 |
+
};
|
| 211 |
+
return codeFrameColumns(rawLines, location, opts);
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
exports.codeFrameColumns = codeFrameColumns;
|
| 215 |
+
exports.default = index;
|
| 216 |
+
exports.highlight = highlight;
|
| 217 |
+
//# sourceMappingURL=index.js.map
|
dashboard/frontend/node_modules/@babel/code-frame/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"version":3,"file":"index.js","sources":["../src/defs.ts","../src/highlight.ts","../src/index.ts"],"sourcesContent":["import picocolors, { createColors } from \"picocolors\";\nimport type { Colors, Formatter } from \"picocolors/types\";\n\nexport function isColorSupported() {\n return (\n // See https://github.com/alexeyraspopov/picocolors/issues/62\n typeof process === \"object\" &&\n (process.env.FORCE_COLOR === \"0\" || process.env.FORCE_COLOR === \"false\")\n ? false\n : picocolors.isColorSupported\n );\n}\n\nexport type InternalTokenType =\n | \"keyword\"\n | \"capitalized\"\n | \"jsxIdentifier\"\n | \"punctuator\"\n | \"number\"\n | \"string\"\n | \"regex\"\n | \"comment\"\n | \"invalid\";\n\ntype UITokens = \"gutter\" | \"marker\" | \"message\";\n\nexport type Defs = Record<InternalTokenType | UITokens | \"reset\", Formatter>;\n\nconst compose: <T, U, V>(f: (gv: U) => V, g: (v: T) => U) => (v: T) => V =\n (f, g) => v =>\n f(g(v));\n\n/**\n * Styles for token types.\n */\nfunction buildDefs(colors: Colors): Defs {\n return {\n keyword: colors.cyan,\n capitalized: colors.yellow,\n jsxIdentifier: colors.yellow,\n punctuator: colors.yellow,\n number: colors.magenta,\n string: colors.green,\n regex: colors.magenta,\n comment: colors.gray,\n invalid: compose(compose(colors.white, colors.bgRed), colors.bold),\n\n gutter: colors.gray,\n marker: compose(colors.red, colors.bold),\n message: compose(colors.red, colors.bold),\n\n reset: colors.reset,\n };\n}\n\nconst defsOn = buildDefs(createColors(true));\nconst defsOff = buildDefs(createColors(false));\n\nexport function getDefs(enabled: boolean): Defs {\n return enabled ? defsOn : defsOff;\n}\n","import type { Token as JSToken, JSXToken } from \"js-tokens\";\nimport jsTokens from \"js-tokens\";\n// We inline this package\n// eslint-disable-next-line import/no-extraneous-dependencies\nimport * as charCodes from \"charcodes\";\n\nimport {\n isStrictReservedWord,\n isKeyword,\n} from \"@babel/helper-validator-identifier\";\n\nimport { getDefs, type InternalTokenType } from \"./defs.ts\";\n\n/**\n * Names that are always allowed as identifiers, but also appear as keywords\n * within certain syntactic productions.\n *\n * https://tc39.es/ecma262/#sec-keywords-and-reserved-words\n *\n * `target` has been omitted since it is very likely going to be a false\n * positive.\n */\nconst sometimesKeywords = new Set([\"as\", \"async\", \"from\", \"get\", \"of\", \"set\"]);\n\ntype Token = {\n type: InternalTokenType | \"uncolored\";\n value: string;\n};\n\n/**\n * RegExp to test for newlines in terminal.\n */\nconst NEWLINE = /\\r\\n|[\\n\\r\\u2028\\u2029]/;\n\n/**\n * RegExp to test for the three types of brackets.\n */\nconst BRACKET = /^[()[\\]{}]$/;\n\nlet tokenize: (\n text: string,\n) => Generator<{ type: InternalTokenType | \"uncolored\"; value: string }>;\n\nif (process.env.BABEL_8_BREAKING) {\n /**\n * Get the type of token, specifying punctuator type.\n */\n const getTokenType = function (\n token: JSToken | JSXToken,\n ): InternalTokenType | \"uncolored\" {\n if (token.type === \"IdentifierName\") {\n const tokenValue = token.value;\n if (\n isKeyword(tokenValue) ||\n isStrictReservedWord(tokenValue, true) ||\n sometimesKeywords.has(tokenValue)\n ) {\n return \"keyword\";\n }\n\n const firstChar = tokenValue.charCodeAt(0);\n if (firstChar < 128) {\n // ASCII characters\n if (\n firstChar >= charCodes.uppercaseA &&\n firstChar <= charCodes.uppercaseZ\n ) {\n return \"capitalized\";\n }\n } else {\n const firstChar = String.fromCodePoint(tokenValue.codePointAt(0));\n if (firstChar !== firstChar.toLowerCase()) {\n return \"capitalized\";\n }\n }\n }\n\n if (token.type === \"Punctuator\" && BRACKET.test(token.value)) {\n return \"uncolored\";\n }\n\n if (token.type === \"Invalid\" && token.value === \"@\") {\n return \"punctuator\";\n }\n\n switch (token.type) {\n case \"NumericLiteral\":\n return \"number\";\n\n case \"StringLiteral\":\n case \"JSXString\":\n case \"NoSubstitutionTemplate\":\n return \"string\";\n\n case \"RegularExpressionLiteral\":\n return \"regex\";\n\n case \"Punctuator\":\n case \"JSXPunctuator\":\n return \"punctuator\";\n\n case \"MultiLineComment\":\n case \"SingleLineComment\":\n return \"comment\";\n\n case \"Invalid\":\n case \"JSXInvalid\":\n return \"invalid\";\n\n case \"JSXIdentifier\":\n return \"jsxIdentifier\";\n\n default:\n return \"uncolored\";\n }\n };\n\n /**\n * Turn a string of JS into an array of objects.\n */\n tokenize = function* (text: string): Generator<Token> {\n for (const token of jsTokens(text, { jsx: true })) {\n switch (token.type) {\n case \"TemplateHead\":\n yield { type: \"string\", value: token.value.slice(0, -2) };\n yield { type: \"punctuator\", value: \"${\" };\n break;\n\n case \"TemplateMiddle\":\n yield { type: \"punctuator\", value: \"}\" };\n yield { type: \"string\", value: token.value.slice(1, -2) };\n yield { type: \"punctuator\", value: \"${\" };\n break;\n\n case \"TemplateTail\":\n yield { type: \"punctuator\", value: \"}\" };\n yield { type: \"string\", value: token.value.slice(1) };\n break;\n\n default:\n yield {\n type: getTokenType(token),\n value: token.value,\n };\n }\n }\n };\n} else {\n /**\n * RegExp to test for what seems to be a JSX tag name.\n */\n const JSX_TAG = /^[a-z][\\w-]*$/i;\n\n // The token here is defined in js-tokens@4. However we don't bother\n // typing it since the whole block will be removed in Babel 8\n const getTokenType = function (token: any, offset: number, text: string) {\n if (token.type === \"name\") {\n const tokenValue = token.value;\n if (\n isKeyword(tokenValue) ||\n isStrictReservedWord(tokenValue, true) ||\n sometimesKeywords.has(tokenValue)\n ) {\n return \"keyword\";\n }\n\n if (\n JSX_TAG.test(tokenValue) &&\n (text[offset - 1] === \"<\" || text.slice(offset - 2, offset) === \"</\")\n ) {\n return \"jsxIdentifier\";\n }\n\n const firstChar = String.fromCodePoint(tokenValue.codePointAt(0));\n if (firstChar !== firstChar.toLowerCase()) {\n return \"capitalized\";\n }\n }\n\n if (token.type === \"punctuator\" && BRACKET.test(token.value)) {\n return \"bracket\";\n }\n\n if (\n token.type === \"invalid\" &&\n (token.value === \"@\" || token.value === \"#\")\n ) {\n return \"punctuator\";\n }\n\n return token.type;\n };\n\n tokenize = function* (text: string) {\n let match;\n while ((match = (jsTokens as any).default.exec(text))) {\n const token = (jsTokens as any).matchToToken(match);\n\n yield {\n type: getTokenType(token, match.index, text),\n value: token.value,\n };\n }\n };\n}\n\nexport function highlight(text: string) {\n if (text === \"\") return \"\";\n\n const defs = getDefs(true);\n\n let highlighted = \"\";\n\n for (const { type, value } of tokenize(text)) {\n if (type in defs) {\n highlighted += value\n .split(NEWLINE)\n .map(str => defs[type as InternalTokenType](str))\n .join(\"\\n\");\n } else {\n highlighted += value;\n }\n }\n\n return highlighted;\n}\n","import { getDefs, isColorSupported } from \"./defs.ts\";\nimport { highlight } from \"./highlight.ts\";\n\nexport { highlight };\n\nlet deprecationWarningShown = false;\n\ntype Location = {\n column: number;\n line: number;\n};\n\ntype NodeLocation = {\n end?: Location;\n start: Location;\n};\n\nexport interface Options {\n /** Syntax highlight the code as JavaScript for terminals. default: false */\n highlightCode?: boolean;\n /** The number of lines to show above the error. default: 2 */\n linesAbove?: number;\n /** The number of lines to show below the error. default: 3 */\n linesBelow?: number;\n /** The line number corresponding to the first line in `rawLines`. default: 1 */\n startLine?: number;\n /**\n * Forcibly syntax highlight the code as JavaScript (for non-terminals);\n * overrides highlightCode.\n * default: false\n */\n forceColor?: boolean;\n /**\n * Pass in a string to be displayed inline (if possible) next to the\n * highlighted location in the code. If it can't be positioned inline,\n * it will be placed above the code frame.\n * default: nothing\n */\n message?: string;\n}\n\n/**\n * RegExp to test for newlines in terminal.\n */\n\nconst NEWLINE = /\\r\\n|[\\n\\r\\u2028\\u2029]/;\n\n/**\n * Extract what lines should be marked and highlighted.\n */\n\ntype MarkerLines = Record<number, true | [number, number]>;\n\nfunction getMarkerLines(\n loc: NodeLocation,\n source: string[],\n opts: Options,\n startLineBaseZero: number,\n): {\n start: number;\n end: number;\n markerLines: MarkerLines;\n} {\n const startLoc: Location = {\n column: 0,\n line: -1,\n ...loc.start,\n };\n const endLoc: Location = {\n ...startLoc,\n ...loc.end,\n };\n const { linesAbove = 2, linesBelow = 3 } = opts || {};\n const startLine = startLoc.line - startLineBaseZero;\n const startColumn = startLoc.column;\n const endLine = endLoc.line - startLineBaseZero;\n const endColumn = endLoc.column;\n\n let start = Math.max(startLine - (linesAbove + 1), 0);\n let end = Math.min(source.length, endLine + linesBelow);\n\n if (startLine === -1) {\n start = 0;\n }\n\n if (endLine === -1) {\n end = source.length;\n }\n\n const lineDiff = endLine - startLine;\n const markerLines: MarkerLines = {};\n\n if (lineDiff) {\n for (let i = 0; i <= lineDiff; i++) {\n const lineNumber = i + startLine;\n\n if (!startColumn) {\n markerLines[lineNumber] = true;\n } else if (i === 0) {\n const sourceLength = source[lineNumber - 1].length;\n\n markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1];\n } else if (i === lineDiff) {\n markerLines[lineNumber] = [0, endColumn];\n } else {\n const sourceLength = source[lineNumber - i].length;\n\n markerLines[lineNumber] = [0, sourceLength];\n }\n }\n } else {\n if (startColumn === endColumn) {\n if (startColumn) {\n markerLines[startLine] = [startColumn, 0];\n } else {\n markerLines[startLine] = true;\n }\n } else {\n markerLines[startLine] = [startColumn, endColumn - startColumn];\n }\n }\n\n return { start, end, markerLines };\n}\n\nexport function codeFrameColumns(\n rawLines: string,\n loc: NodeLocation,\n opts: Options = {},\n): string {\n const shouldHighlight =\n opts.forceColor || (isColorSupported() && opts.highlightCode);\n const startLineBaseZero = (opts.startLine || 1) - 1;\n const defs = getDefs(shouldHighlight);\n\n const lines = rawLines.split(NEWLINE);\n const { start, end, markerLines } = getMarkerLines(\n loc,\n lines,\n opts,\n startLineBaseZero,\n );\n const hasColumns = loc.start && typeof loc.start.column === \"number\";\n\n const numberMaxWidth = String(end + startLineBaseZero).length;\n\n const highlightedLines = shouldHighlight ? highlight(rawLines) : rawLines;\n\n let frame = highlightedLines\n .split(NEWLINE, end)\n .slice(start, end)\n .map((line, index) => {\n const number = start + 1 + index;\n const paddedNumber = ` ${number + startLineBaseZero}`.slice(\n -numberMaxWidth,\n );\n const gutter = ` ${paddedNumber} |`;\n const hasMarker = markerLines[number];\n const lastMarkerLine = !markerLines[number + 1];\n if (hasMarker) {\n let markerLine = \"\";\n if (Array.isArray(hasMarker)) {\n const markerSpacing = line\n .slice(0, Math.max(hasMarker[0] - 1, 0))\n .replace(/[^\\t]/g, \" \");\n const numberOfMarkers = hasMarker[1] || 1;\n\n markerLine = [\n \"\\n \",\n defs.gutter(gutter.replace(/\\d/g, \" \")),\n \" \",\n markerSpacing,\n defs.marker(\"^\").repeat(numberOfMarkers),\n ].join(\"\");\n\n if (lastMarkerLine && opts.message) {\n markerLine += \" \" + defs.message(opts.message);\n }\n }\n return [\n defs.marker(\">\"),\n defs.gutter(gutter),\n line.length > 0 ? ` ${line}` : \"\",\n markerLine,\n ].join(\"\");\n } else {\n return ` ${defs.gutter(gutter)}${line.length > 0 ? ` ${line}` : \"\"}`;\n }\n })\n .join(\"\\n\");\n\n if (opts.message && !hasColumns) {\n frame = `${\" \".repeat(numberMaxWidth + 1)}${opts.message}\\n${frame}`;\n }\n\n if (shouldHighlight) {\n return defs.reset(frame);\n } else {\n return frame;\n }\n}\n\n/**\n * Create a code frame, adding line numbers, code highlighting, and pointing to a given position.\n */\n\nexport default function (\n rawLines: string,\n lineNumber: number,\n colNumber?: number | null,\n opts: Options = {},\n): string {\n if (!deprecationWarningShown) {\n deprecationWarningShown = true;\n\n const message =\n \"Passing lineNumber and colNumber is deprecated to @babel/code-frame. Please use `codeFrameColumns`.\";\n\n if (process.emitWarning) {\n // A string is directly supplied to emitWarning, because when supplying an\n // Error object node throws in the tests because of different contexts\n process.emitWarning(message, \"DeprecationWarning\");\n } else {\n const deprecationError = new Error(message);\n deprecationError.name = \"DeprecationWarning\";\n console.warn(new Error(message));\n }\n }\n\n colNumber = Math.max(colNumber, 0);\n\n const location: NodeLocation = {\n start: { column: colNumber, line: lineNumber },\n };\n\n return codeFrameColumns(rawLines, location, opts);\n}\n"],"names":["isColorSupported","process","env","FORCE_COLOR","picocolors","compose","f","g","v","buildDefs","colors","keyword","cyan","capitalized","yellow","jsxIdentifier","punctuator","number","magenta","string","green","regex","comment","gray","invalid","white","bgRed","bold","gutter","marker","red","message","reset","defsOn","createColors","defsOff","getDefs","enabled","sometimesKeywords","Set","NEWLINE","BRACKET","tokenize","JSX_TAG","getTokenType","token","offset","text","type","tokenValue","value","isKeyword","isStrictReservedWord","has","test","slice","firstChar","String","fromCodePoint","codePointAt","toLowerCase","match","jsTokens","default","exec","matchToToken","index","highlight","defs","highlighted","split","map","str","join","deprecationWarningShown","getMarkerLines","loc","source","opts","startLineBaseZero","startLoc","Object","assign","column","line","start","endLoc","end","linesAbove","linesBelow","startLine","startColumn","endLine","endColumn","Math","max","min","length","lineDiff","markerLines","i","lineNumber","sourceLength","codeFrameColumns","rawLines","shouldHighlight","forceColor","highlightCode","lines","hasColumns","numberMaxWidth","highlightedLines","frame","paddedNumber","hasMarker","lastMarkerLine","markerLine","Array","isArray","markerSpacing","replace","numberOfMarkers","repeat","colNumber","emitWarning","deprecationError","Error","name","console","warn","location"],"mappings":";;;;;;;;AAGO,SAASA,gBAAgBA,GAAG;EACjC,QAEE,OAAOC,OAAO,KAAK,QAAQ,KACxBA,OAAO,CAACC,GAAG,CAACC,WAAW,KAAK,GAAG,IAAIF,OAAO,CAACC,GAAG,CAACC,WAAW,KAAK,OAAO,CAAC,GACtE,KAAK,GACLC,UAAU,CAACJ,gBAAAA;AAAgB,IAAA;AAEnC,CAAA;AAiBA,MAAMK,OAAkE,GACtEA,CAACC,CAAC,EAAEC,CAAC,KAAKC,CAAC,IACTF,CAAC,CAACC,CAAC,CAACC,CAAC,CAAC,CAAC,CAAA;AAKX,SAASC,SAASA,CAACC,MAAc,EAAQ;EACvC,OAAO;IACLC,OAAO,EAAED,MAAM,CAACE,IAAI;IACpBC,WAAW,EAAEH,MAAM,CAACI,MAAM;IAC1BC,aAAa,EAAEL,MAAM,CAACI,MAAM;IAC5BE,UAAU,EAAEN,MAAM,CAACI,MAAM;IACzBG,MAAM,EAAEP,MAAM,CAACQ,OAAO;IACtBC,MAAM,EAAET,MAAM,CAACU,KAAK;IACpBC,KAAK,EAAEX,MAAM,CAACQ,OAAO;IACrBI,OAAO,EAAEZ,MAAM,CAACa,IAAI;AACpBC,IAAAA,OAAO,EAAEnB,OAAO,CAACA,OAAO,CAACK,MAAM,CAACe,KAAK,EAAEf,MAAM,CAACgB,KAAK,CAAC,EAAEhB,MAAM,CAACiB,IAAI,CAAC;IAElEC,MAAM,EAAElB,MAAM,CAACa,IAAI;IACnBM,MAAM,EAAExB,OAAO,CAACK,MAAM,CAACoB,GAAG,EAAEpB,MAAM,CAACiB,IAAI,CAAC;IACxCI,OAAO,EAAE1B,OAAO,CAACK,MAAM,CAACoB,GAAG,EAAEpB,MAAM,CAACiB,IAAI,CAAC;IAEzCK,KAAK,EAAEtB,MAAM,CAACsB,KAAAA;GACf,CAAA;AACH,CAAA;AAEA,MAAMC,MAAM,GAAGxB,SAAS,CAACyB,uBAAY,CAAC,IAAI,CAAC,CAAC,CAAA;AAC5C,MAAMC,OAAO,GAAG1B,SAAS,CAACyB,uBAAY,CAAC,KAAK,CAAC,CAAC,CAAA;AAEvC,SAASE,OAAOA,CAACC,OAAgB,EAAQ;AAC9C,EAAA,OAAOA,OAAO,GAAGJ,MAAM,GAAGE,OAAO,CAAA;AACnC;;ACtCA,MAAMG,iBAAiB,GAAG,IAAIC,GAAG,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAA;AAU9E,MAAMC,SAAO,GAAG,yBAAyB,CAAA;AAKzC,MAAMC,OAAO,GAAG,aAAa,CAAA;AAE7B,IAAIC,QAEoE,CAAA;AA8GtE,MAAMC,OAAO,GAAG,gBAAgB,CAAA;AAIhC,MAAMC,YAAY,GAAG,UAAUC,KAAU,EAAEC,MAAc,EAAEC,IAAY,EAAE;AACvE,EAAA,IAAIF,KAAK,CAACG,IAAI,KAAK,MAAM,EAAE;AACzB,IAAA,MAAMC,UAAU,GAAGJ,KAAK,CAACK,KAAK,CAAA;AAC9B,IAAA,IACEC,mCAAS,CAACF,UAAU,CAAC,IACrBG,8CAAoB,CAACH,UAAU,EAAE,IAAI,CAAC,IACtCX,iBAAiB,CAACe,GAAG,CAACJ,UAAU,CAAC,EACjC;AACA,MAAA,OAAO,SAAS,CAAA;AAClB,KAAA;AAEA,IAAA,IACEN,OAAO,CAACW,IAAI,CAACL,UAAU,CAAC,KACvBF,IAAI,CAACD,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG,IAAIC,IAAI,CAACQ,KAAK,CAACT,MAAM,GAAG,CAAC,EAAEA,MAAM,CAAC,KAAK,IAAI,CAAC,EACrE;AACA,MAAA,OAAO,eAAe,CAAA;AACxB,KAAA;AAEA,IAAA,MAAMU,SAAS,GAAGC,MAAM,CAACC,aAAa,CAACT,UAAU,CAACU,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;AACjE,IAAA,IAAIH,SAAS,KAAKA,SAAS,CAACI,WAAW,EAAE,EAAE;AACzC,MAAA,OAAO,aAAa,CAAA;AACtB,KAAA;AACF,GAAA;AAEA,EAAA,IAAIf,KAAK,CAACG,IAAI,KAAK,YAAY,IAAIP,OAAO,CAACa,IAAI,CAACT,KAAK,CAACK,KAAK,CAAC,EAAE;AAC5D,IAAA,OAAO,SAAS,CAAA;AAClB,GAAA;AAEA,EAAA,IACEL,KAAK,CAACG,IAAI,KAAK,SAAS,KACvBH,KAAK,CAACK,KAAK,KAAK,GAAG,IAAIL,KAAK,CAACK,KAAK,KAAK,GAAG,CAAC,EAC5C;AACA,IAAA,OAAO,YAAY,CAAA;AACrB,GAAA;EAEA,OAAOL,KAAK,CAACG,IAAI,CAAA;AACnB,CAAC,CAAA;AAEDN,QAAQ,GAAG,WAAWK,IAAY,EAAE;AAClC,EAAA,IAAIc,KAAK,CAAA;EACT,OAAQA,KAAK,GAAIC,QAAQ,CAASC,OAAO,CAACC,IAAI,CAACjB,IAAI,CAAC,EAAG;AACrD,IAAA,MAAMF,KAAK,GAAIiB,QAAQ,CAASG,YAAY,CAACJ,KAAK,CAAC,CAAA;IAEnD,MAAM;MACJb,IAAI,EAAEJ,YAAY,CAACC,KAAK,EAAEgB,KAAK,CAACK,KAAK,EAAEnB,IAAI,CAAC;MAC5CG,KAAK,EAAEL,KAAK,CAACK,KAAAA;KACd,CAAA;AACH,GAAA;AACF,CAAC,CAAA;AAGI,SAASiB,SAASA,CAACpB,IAAY,EAAE;AACtC,EAAA,IAAIA,IAAI,KAAK,EAAE,EAAE,OAAO,EAAE,CAAA;AAE1B,EAAA,MAAMqB,IAAI,GAAGhC,OAAO,CAAC,IAAI,CAAC,CAAA;EAE1B,IAAIiC,WAAW,GAAG,EAAE,CAAA;AAEpB,EAAA,KAAK,MAAM;IAAErB,IAAI;AAAEE,IAAAA,KAAAA;AAAM,GAAC,IAAIR,QAAQ,CAACK,IAAI,CAAC,EAAE;IAC5C,IAAIC,IAAI,IAAIoB,IAAI,EAAE;MAChBC,WAAW,IAAInB,KAAK,CACjBoB,KAAK,CAAC9B,SAAO,CAAC,CACd+B,GAAG,CAACC,GAAG,IAAIJ,IAAI,CAACpB,IAAI,CAAsB,CAACwB,GAAG,CAAC,CAAC,CAChDC,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,KAAC,MAAM;AACLJ,MAAAA,WAAW,IAAInB,KAAK,CAAA;AACtB,KAAA;AACF,GAAA;AAEA,EAAA,OAAOmB,WAAW,CAAA;AACpB;;AC5NA,IAAIK,uBAAuB,GAAG,KAAK,CAAA;AAwCnC,MAAMlC,OAAO,GAAG,yBAAyB,CAAA;AAQzC,SAASmC,cAAcA,CACrBC,GAAiB,EACjBC,MAAgB,EAChBC,IAAa,EACbC,iBAAyB,EAKzB;AACA,EAAA,MAAMC,QAAkB,GAAAC,MAAA,CAAAC,MAAA,CAAA;AACtBC,IAAAA,MAAM,EAAE,CAAC;AACTC,IAAAA,IAAI,EAAE,CAAC,CAAA;GACJR,EAAAA,GAAG,CAACS,KAAK,CACb,CAAA;EACD,MAAMC,MAAgB,GAAAL,MAAA,CAAAC,MAAA,CACjBF,EAAAA,EAAAA,QAAQ,EACRJ,GAAG,CAACW,GAAG,CACX,CAAA;EACD,MAAM;AAAEC,IAAAA,UAAU,GAAG,CAAC;AAAEC,IAAAA,UAAU,GAAG,CAAA;AAAE,GAAC,GAAGX,IAAI,IAAI,EAAE,CAAA;AACrD,EAAA,MAAMY,SAAS,GAAGV,QAAQ,CAACI,IAAI,GAAGL,iBAAiB,CAAA;AACnD,EAAA,MAAMY,WAAW,GAAGX,QAAQ,CAACG,MAAM,CAAA;AACnC,EAAA,MAAMS,OAAO,GAAGN,MAAM,CAACF,IAAI,GAAGL,iBAAiB,CAAA;AAC/C,EAAA,MAAMc,SAAS,GAAGP,MAAM,CAACH,MAAM,CAAA;AAE/B,EAAA,IAAIE,KAAK,GAAGS,IAAI,CAACC,GAAG,CAACL,SAAS,IAAIF,UAAU,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AACrD,EAAA,IAAID,GAAG,GAAGO,IAAI,CAACE,GAAG,CAACnB,MAAM,CAACoB,MAAM,EAAEL,OAAO,GAAGH,UAAU,CAAC,CAAA;AAEvD,EAAA,IAAIC,SAAS,KAAK,CAAC,CAAC,EAAE;AACpBL,IAAAA,KAAK,GAAG,CAAC,CAAA;AACX,GAAA;AAEA,EAAA,IAAIO,OAAO,KAAK,CAAC,CAAC,EAAE;IAClBL,GAAG,GAAGV,MAAM,CAACoB,MAAM,CAAA;AACrB,GAAA;AAEA,EAAA,MAAMC,QAAQ,GAAGN,OAAO,GAAGF,SAAS,CAAA;EACpC,MAAMS,WAAwB,GAAG,EAAE,CAAA;AAEnC,EAAA,IAAID,QAAQ,EAAE;IACZ,KAAK,IAAIE,CAAC,GAAG,CAAC,EAAEA,CAAC,IAAIF,QAAQ,EAAEE,CAAC,EAAE,EAAE;AAClC,MAAA,MAAMC,UAAU,GAAGD,CAAC,GAAGV,SAAS,CAAA;MAEhC,IAAI,CAACC,WAAW,EAAE;AAChBQ,QAAAA,WAAW,CAACE,UAAU,CAAC,GAAG,IAAI,CAAA;AAChC,OAAC,MAAM,IAAID,CAAC,KAAK,CAAC,EAAE;QAClB,MAAME,YAAY,GAAGzB,MAAM,CAACwB,UAAU,GAAG,CAAC,CAAC,CAACJ,MAAM,CAAA;AAElDE,QAAAA,WAAW,CAACE,UAAU,CAAC,GAAG,CAACV,WAAW,EAAEW,YAAY,GAAGX,WAAW,GAAG,CAAC,CAAC,CAAA;AACzE,OAAC,MAAM,IAAIS,CAAC,KAAKF,QAAQ,EAAE;QACzBC,WAAW,CAACE,UAAU,CAAC,GAAG,CAAC,CAAC,EAAER,SAAS,CAAC,CAAA;AAC1C,OAAC,MAAM;QACL,MAAMS,YAAY,GAAGzB,MAAM,CAACwB,UAAU,GAAGD,CAAC,CAAC,CAACH,MAAM,CAAA;QAElDE,WAAW,CAACE,UAAU,CAAC,GAAG,CAAC,CAAC,EAAEC,YAAY,CAAC,CAAA;AAC7C,OAAA;AACF,KAAA;AACF,GAAC,MAAM;IACL,IAAIX,WAAW,KAAKE,SAAS,EAAE;AAC7B,MAAA,IAAIF,WAAW,EAAE;QACfQ,WAAW,CAACT,SAAS,CAAC,GAAG,CAACC,WAAW,EAAE,CAAC,CAAC,CAAA;AAC3C,OAAC,MAAM;AACLQ,QAAAA,WAAW,CAACT,SAAS,CAAC,GAAG,IAAI,CAAA;AAC/B,OAAA;AACF,KAAC,MAAM;MACLS,WAAW,CAACT,SAAS,CAAC,GAAG,CAACC,WAAW,EAAEE,SAAS,GAAGF,WAAW,CAAC,CAAA;AACjE,KAAA;AACF,GAAA;EAEA,OAAO;IAAEN,KAAK;IAAEE,GAAG;AAAEY,IAAAA,WAAAA;GAAa,CAAA;AACpC,CAAA;AAEO,SAASI,gBAAgBA,CAC9BC,QAAgB,EAChB5B,GAAiB,EACjBE,IAAa,GAAG,EAAE,EACV;AACR,EAAA,MAAM2B,eAAe,GACnB3B,IAAI,CAAC4B,UAAU,IAAK1G,gBAAgB,EAAE,IAAI8E,IAAI,CAAC6B,aAAc,CAAA;EAC/D,MAAM5B,iBAAiB,GAAG,CAACD,IAAI,CAACY,SAAS,IAAI,CAAC,IAAI,CAAC,CAAA;AACnD,EAAA,MAAMtB,IAAI,GAAGhC,OAAO,CAACqE,eAAe,CAAC,CAAA;AAErC,EAAA,MAAMG,KAAK,GAAGJ,QAAQ,CAAClC,KAAK,CAAC9B,OAAO,CAAC,CAAA;EACrC,MAAM;IAAE6C,KAAK;IAAEE,GAAG;AAAEY,IAAAA,WAAAA;GAAa,GAAGxB,cAAc,CAChDC,GAAG,EACHgC,KAAK,EACL9B,IAAI,EACJC,iBACF,CAAC,CAAA;AACD,EAAA,MAAM8B,UAAU,GAAGjC,GAAG,CAACS,KAAK,IAAI,OAAOT,GAAG,CAACS,KAAK,CAACF,MAAM,KAAK,QAAQ,CAAA;EAEpE,MAAM2B,cAAc,GAAGrD,MAAM,CAAC8B,GAAG,GAAGR,iBAAiB,CAAC,CAACkB,MAAM,CAAA;EAE7D,MAAMc,gBAAgB,GAAGN,eAAe,GAAGtC,SAAS,CAACqC,QAAQ,CAAC,GAAGA,QAAQ,CAAA;EAEzE,IAAIQ,KAAK,GAAGD,gBAAgB,CACzBzC,KAAK,CAAC9B,OAAO,EAAE+C,GAAG,CAAC,CACnBhC,KAAK,CAAC8B,KAAK,EAAEE,GAAG,CAAC,CACjBhB,GAAG,CAAC,CAACa,IAAI,EAAElB,KAAK,KAAK;AACpB,IAAA,MAAMjD,MAAM,GAAGoE,KAAK,GAAG,CAAC,GAAGnB,KAAK,CAAA;AAChC,IAAA,MAAM+C,YAAY,GAAG,CAAIhG,CAAAA,EAAAA,MAAM,GAAG8D,iBAAiB,CAAE,CAAA,CAACxB,KAAK,CACzD,CAACuD,cACH,CAAC,CAAA;AACD,IAAA,MAAMlF,MAAM,GAAG,CAAIqF,CAAAA,EAAAA,YAAY,CAAI,EAAA,CAAA,CAAA;AACnC,IAAA,MAAMC,SAAS,GAAGf,WAAW,CAAClF,MAAM,CAAC,CAAA;IACrC,MAAMkG,cAAc,GAAG,CAAChB,WAAW,CAAClF,MAAM,GAAG,CAAC,CAAC,CAAA;AAC/C,IAAA,IAAIiG,SAAS,EAAE;MACb,IAAIE,UAAU,GAAG,EAAE,CAAA;AACnB,MAAA,IAAIC,KAAK,CAACC,OAAO,CAACJ,SAAS,CAAC,EAAE;AAC5B,QAAA,MAAMK,aAAa,GAAGnC,IAAI,CACvB7B,KAAK,CAAC,CAAC,EAAEuC,IAAI,CAACC,GAAG,CAACmB,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CACvCM,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAA;AACzB,QAAA,MAAMC,eAAe,GAAGP,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;AAEzCE,QAAAA,UAAU,GAAG,CACX,KAAK,EACLhD,IAAI,CAACxC,MAAM,CAACA,MAAM,CAAC4F,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,EACvC,GAAG,EACHD,aAAa,EACbnD,IAAI,CAACvC,MAAM,CAAC,GAAG,CAAC,CAAC6F,MAAM,CAACD,eAAe,CAAC,CACzC,CAAChD,IAAI,CAAC,EAAE,CAAC,CAAA;AAEV,QAAA,IAAI0C,cAAc,IAAIrC,IAAI,CAAC/C,OAAO,EAAE;UAClCqF,UAAU,IAAI,GAAG,GAAGhD,IAAI,CAACrC,OAAO,CAAC+C,IAAI,CAAC/C,OAAO,CAAC,CAAA;AAChD,SAAA;AACF,OAAA;AACA,MAAA,OAAO,CACLqC,IAAI,CAACvC,MAAM,CAAC,GAAG,CAAC,EAChBuC,IAAI,CAACxC,MAAM,CAACA,MAAM,CAAC,EACnBwD,IAAI,CAACa,MAAM,GAAG,CAAC,GAAG,CAAA,CAAA,EAAIb,IAAI,CAAE,CAAA,GAAG,EAAE,EACjCgC,UAAU,CACX,CAAC3C,IAAI,CAAC,EAAE,CAAC,CAAA;AACZ,KAAC,MAAM;AACL,MAAA,OAAO,IAAIL,IAAI,CAACxC,MAAM,CAACA,MAAM,CAAC,CAAGwD,EAAAA,IAAI,CAACa,MAAM,GAAG,CAAC,GAAG,CAAA,CAAA,EAAIb,IAAI,CAAE,CAAA,GAAG,EAAE,CAAE,CAAA,CAAA;AACtE,KAAA;AACF,GAAC,CAAC,CACDX,IAAI,CAAC,IAAI,CAAC,CAAA;AAEb,EAAA,IAAIK,IAAI,CAAC/C,OAAO,IAAI,CAAC8E,UAAU,EAAE;AAC/BG,IAAAA,KAAK,GAAG,CAAG,EAAA,GAAG,CAACU,MAAM,CAACZ,cAAc,GAAG,CAAC,CAAC,GAAGhC,IAAI,CAAC/C,OAAO,CAAA,EAAA,EAAKiF,KAAK,CAAE,CAAA,CAAA;AACtE,GAAA;AAEA,EAAA,IAAIP,eAAe,EAAE;AACnB,IAAA,OAAOrC,IAAI,CAACpC,KAAK,CAACgF,KAAK,CAAC,CAAA;AAC1B,GAAC,MAAM;AACL,IAAA,OAAOA,KAAK,CAAA;AACd,GAAA;AACF,CAAA;AAMe,cAAA,EACbR,QAAgB,EAChBH,UAAkB,EAClBsB,SAAyB,EACzB7C,IAAa,GAAG,EAAE,EACV;EACR,IAAI,CAACJ,uBAAuB,EAAE;AAC5BA,IAAAA,uBAAuB,GAAG,IAAI,CAAA;IAE9B,MAAM3C,OAAO,GACX,qGAAqG,CAAA;IAEvG,IAAI9B,OAAO,CAAC2H,WAAW,EAAE;AAGvB3H,MAAAA,OAAO,CAAC2H,WAAW,CAAC7F,OAAO,EAAE,oBAAoB,CAAC,CAAA;AACpD,KAAC,MAAM;AACL,MAAA,MAAM8F,gBAAgB,GAAG,IAAIC,KAAK,CAAC/F,OAAO,CAAC,CAAA;MAC3C8F,gBAAgB,CAACE,IAAI,GAAG,oBAAoB,CAAA;MAC5CC,OAAO,CAACC,IAAI,CAAC,IAAIH,KAAK,CAAC/F,OAAO,CAAC,CAAC,CAAA;AAClC,KAAA;AACF,GAAA;EAEA4F,SAAS,GAAG7B,IAAI,CAACC,GAAG,CAAC4B,SAAS,EAAE,CAAC,CAAC,CAAA;AAElC,EAAA,MAAMO,QAAsB,GAAG;AAC7B7C,IAAAA,KAAK,EAAE;AAAEF,MAAAA,MAAM,EAAEwC,SAAS;AAAEvC,MAAAA,IAAI,EAAEiB,UAAAA;AAAW,KAAA;GAC9C,CAAA;AAED,EAAA,OAAOE,gBAAgB,CAACC,QAAQ,EAAE0B,QAAQ,EAAEpD,IAAI,CAAC,CAAA;AACnD;;;;;;"}
|
dashboard/frontend/node_modules/@babel/code-frame/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "@babel/code-frame",
|
| 3 |
+
"version": "7.29.0",
|
| 4 |
+
"description": "Generate errors that contain a code frame that point to source locations.",
|
| 5 |
+
"author": "The Babel Team (https://babel.dev/team)",
|
| 6 |
+
"homepage": "https://babel.dev/docs/en/next/babel-code-frame",
|
| 7 |
+
"bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen",
|
| 8 |
+
"license": "MIT",
|
| 9 |
+
"publishConfig": {
|
| 10 |
+
"access": "public"
|
| 11 |
+
},
|
| 12 |
+
"repository": {
|
| 13 |
+
"type": "git",
|
| 14 |
+
"url": "https://github.com/babel/babel.git",
|
| 15 |
+
"directory": "packages/babel-code-frame"
|
| 16 |
+
},
|
| 17 |
+
"main": "./lib/index.js",
|
| 18 |
+
"dependencies": {
|
| 19 |
+
"@babel/helper-validator-identifier": "^7.28.5",
|
| 20 |
+
"js-tokens": "^4.0.0",
|
| 21 |
+
"picocolors": "^1.1.1"
|
| 22 |
+
},
|
| 23 |
+
"devDependencies": {
|
| 24 |
+
"charcodes": "^0.2.0",
|
| 25 |
+
"import-meta-resolve": "^4.1.0",
|
| 26 |
+
"strip-ansi": "^4.0.0"
|
| 27 |
+
},
|
| 28 |
+
"engines": {
|
| 29 |
+
"node": ">=6.9.0"
|
| 30 |
+
},
|
| 31 |
+
"type": "commonjs"
|
| 32 |
+
}
|
dashboard/frontend/node_modules/@babel/compat-data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2014-present Sebastian McKenzie and other contributors
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining
|
| 6 |
+
a copy of this software and associated documentation files (the
|
| 7 |
+
"Software"), to deal in the Software without restriction, including
|
| 8 |
+
without limitation the rights to use, copy, modify, merge, publish,
|
| 9 |
+
distribute, sublicense, and/or sell copies of the Software, and to
|
| 10 |
+
permit persons to whom the Software is furnished to do so, subject to
|
| 11 |
+
the following conditions:
|
| 12 |
+
|
| 13 |
+
The above copyright notice and this permission notice shall be
|
| 14 |
+
included in all copies or substantial portions of the Software.
|
| 15 |
+
|
| 16 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 17 |
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 18 |
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 19 |
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 20 |
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 21 |
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 22 |
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
dashboard/frontend/node_modules/@babel/compat-data/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @babel/compat-data
|
| 2 |
+
|
| 3 |
+
> The compat-data to determine required Babel plugins
|
| 4 |
+
|
| 5 |
+
See our website [@babel/compat-data](https://babeljs.io/docs/babel-compat-data) for more information.
|
| 6 |
+
|
| 7 |
+
## Install
|
| 8 |
+
|
| 9 |
+
Using npm:
|
| 10 |
+
|
| 11 |
+
```sh
|
| 12 |
+
npm install --save @babel/compat-data
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
or using yarn:
|
| 16 |
+
|
| 17 |
+
```sh
|
| 18 |
+
yarn add @babel/compat-data
|
| 19 |
+
```
|
dashboard/frontend/node_modules/@babel/compat-data/corejs2-built-ins.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Todo (Babel 8): remove this file as Babel 8 drop support of core-js 2
|
| 2 |
+
module.exports = require("./data/corejs2-built-ins.json");
|
dashboard/frontend/node_modules/@babel/compat-data/corejs3-shipped-proposals.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Todo (Babel 8): remove this file now that it is included in babel-plugin-polyfill-corejs3
|
| 2 |
+
module.exports = require("./data/corejs3-shipped-proposals.json");
|
dashboard/frontend/node_modules/@babel/compat-data/data/corejs2-built-ins.json
ADDED
|
@@ -0,0 +1,2106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"es6.array.copy-within": {
|
| 3 |
+
"chrome": "45",
|
| 4 |
+
"opera": "32",
|
| 5 |
+
"edge": "12",
|
| 6 |
+
"firefox": "32",
|
| 7 |
+
"safari": "9",
|
| 8 |
+
"node": "4",
|
| 9 |
+
"deno": "1",
|
| 10 |
+
"ios": "9",
|
| 11 |
+
"samsung": "5",
|
| 12 |
+
"rhino": "1.7.13",
|
| 13 |
+
"opera_mobile": "32",
|
| 14 |
+
"electron": "0.31"
|
| 15 |
+
},
|
| 16 |
+
"es6.array.every": {
|
| 17 |
+
"chrome": "5",
|
| 18 |
+
"opera": "10.10",
|
| 19 |
+
"edge": "12",
|
| 20 |
+
"firefox": "2",
|
| 21 |
+
"safari": "3.1",
|
| 22 |
+
"node": "0.4",
|
| 23 |
+
"deno": "1",
|
| 24 |
+
"ie": "9",
|
| 25 |
+
"android": "4",
|
| 26 |
+
"ios": "6",
|
| 27 |
+
"phantom": "1.9",
|
| 28 |
+
"samsung": "1",
|
| 29 |
+
"rhino": "1.7.13",
|
| 30 |
+
"opera_mobile": "10.1",
|
| 31 |
+
"electron": "0.20"
|
| 32 |
+
},
|
| 33 |
+
"es6.array.fill": {
|
| 34 |
+
"chrome": "45",
|
| 35 |
+
"opera": "32",
|
| 36 |
+
"edge": "12",
|
| 37 |
+
"firefox": "31",
|
| 38 |
+
"safari": "7.1",
|
| 39 |
+
"node": "4",
|
| 40 |
+
"deno": "1",
|
| 41 |
+
"ios": "8",
|
| 42 |
+
"samsung": "5",
|
| 43 |
+
"rhino": "1.7.13",
|
| 44 |
+
"opera_mobile": "32",
|
| 45 |
+
"electron": "0.31"
|
| 46 |
+
},
|
| 47 |
+
"es6.array.filter": {
|
| 48 |
+
"chrome": "51",
|
| 49 |
+
"opera": "38",
|
| 50 |
+
"edge": "13",
|
| 51 |
+
"firefox": "48",
|
| 52 |
+
"safari": "10",
|
| 53 |
+
"node": "6.5",
|
| 54 |
+
"deno": "1",
|
| 55 |
+
"ios": "10",
|
| 56 |
+
"samsung": "5",
|
| 57 |
+
"opera_mobile": "41",
|
| 58 |
+
"electron": "1.2"
|
| 59 |
+
},
|
| 60 |
+
"es6.array.find": {
|
| 61 |
+
"chrome": "45",
|
| 62 |
+
"opera": "32",
|
| 63 |
+
"edge": "12",
|
| 64 |
+
"firefox": "25",
|
| 65 |
+
"safari": "7.1",
|
| 66 |
+
"node": "4",
|
| 67 |
+
"deno": "1",
|
| 68 |
+
"ios": "8",
|
| 69 |
+
"samsung": "5",
|
| 70 |
+
"rhino": "1.7.13",
|
| 71 |
+
"opera_mobile": "32",
|
| 72 |
+
"electron": "0.31"
|
| 73 |
+
},
|
| 74 |
+
"es6.array.find-index": {
|
| 75 |
+
"chrome": "45",
|
| 76 |
+
"opera": "32",
|
| 77 |
+
"edge": "12",
|
| 78 |
+
"firefox": "25",
|
| 79 |
+
"safari": "7.1",
|
| 80 |
+
"node": "4",
|
| 81 |
+
"deno": "1",
|
| 82 |
+
"ios": "8",
|
| 83 |
+
"samsung": "5",
|
| 84 |
+
"rhino": "1.7.13",
|
| 85 |
+
"opera_mobile": "32",
|
| 86 |
+
"electron": "0.31"
|
| 87 |
+
},
|
| 88 |
+
"es7.array.flat-map": {
|
| 89 |
+
"chrome": "69",
|
| 90 |
+
"opera": "56",
|
| 91 |
+
"edge": "79",
|
| 92 |
+
"firefox": "62",
|
| 93 |
+
"safari": "12",
|
| 94 |
+
"node": "11",
|
| 95 |
+
"deno": "1",
|
| 96 |
+
"ios": "12",
|
| 97 |
+
"samsung": "10",
|
| 98 |
+
"rhino": "1.7.15",
|
| 99 |
+
"opera_mobile": "48",
|
| 100 |
+
"electron": "4.0"
|
| 101 |
+
},
|
| 102 |
+
"es6.array.for-each": {
|
| 103 |
+
"chrome": "5",
|
| 104 |
+
"opera": "10.10",
|
| 105 |
+
"edge": "12",
|
| 106 |
+
"firefox": "2",
|
| 107 |
+
"safari": "3.1",
|
| 108 |
+
"node": "0.4",
|
| 109 |
+
"deno": "1",
|
| 110 |
+
"ie": "9",
|
| 111 |
+
"android": "4",
|
| 112 |
+
"ios": "6",
|
| 113 |
+
"phantom": "1.9",
|
| 114 |
+
"samsung": "1",
|
| 115 |
+
"rhino": "1.7.13",
|
| 116 |
+
"opera_mobile": "10.1",
|
| 117 |
+
"electron": "0.20"
|
| 118 |
+
},
|
| 119 |
+
"es6.array.from": {
|
| 120 |
+
"chrome": "51",
|
| 121 |
+
"opera": "38",
|
| 122 |
+
"edge": "15",
|
| 123 |
+
"firefox": "36",
|
| 124 |
+
"safari": "10",
|
| 125 |
+
"node": "6.5",
|
| 126 |
+
"deno": "1",
|
| 127 |
+
"ios": "10",
|
| 128 |
+
"samsung": "5",
|
| 129 |
+
"rhino": "1.7.15",
|
| 130 |
+
"opera_mobile": "41",
|
| 131 |
+
"electron": "1.2"
|
| 132 |
+
},
|
| 133 |
+
"es7.array.includes": {
|
| 134 |
+
"chrome": "47",
|
| 135 |
+
"opera": "34",
|
| 136 |
+
"edge": "14",
|
| 137 |
+
"firefox": "102",
|
| 138 |
+
"safari": "10",
|
| 139 |
+
"node": "6",
|
| 140 |
+
"deno": "1",
|
| 141 |
+
"ios": "10",
|
| 142 |
+
"samsung": "5",
|
| 143 |
+
"rhino": "1.8",
|
| 144 |
+
"opera_mobile": "34",
|
| 145 |
+
"electron": "0.36"
|
| 146 |
+
},
|
| 147 |
+
"es6.array.index-of": {
|
| 148 |
+
"chrome": "5",
|
| 149 |
+
"opera": "10.10",
|
| 150 |
+
"edge": "12",
|
| 151 |
+
"firefox": "2",
|
| 152 |
+
"safari": "3.1",
|
| 153 |
+
"node": "0.4",
|
| 154 |
+
"deno": "1",
|
| 155 |
+
"ie": "9",
|
| 156 |
+
"android": "4",
|
| 157 |
+
"ios": "6",
|
| 158 |
+
"phantom": "1.9",
|
| 159 |
+
"samsung": "1",
|
| 160 |
+
"rhino": "1.7.13",
|
| 161 |
+
"opera_mobile": "10.1",
|
| 162 |
+
"electron": "0.20"
|
| 163 |
+
},
|
| 164 |
+
"es6.array.is-array": {
|
| 165 |
+
"chrome": "5",
|
| 166 |
+
"opera": "10.50",
|
| 167 |
+
"edge": "12",
|
| 168 |
+
"firefox": "4",
|
| 169 |
+
"safari": "4",
|
| 170 |
+
"node": "0.4",
|
| 171 |
+
"deno": "1",
|
| 172 |
+
"ie": "9",
|
| 173 |
+
"android": "4",
|
| 174 |
+
"ios": "6",
|
| 175 |
+
"phantom": "1.9",
|
| 176 |
+
"samsung": "1",
|
| 177 |
+
"rhino": "1.7.13",
|
| 178 |
+
"opera_mobile": "10.1",
|
| 179 |
+
"electron": "0.20"
|
| 180 |
+
},
|
| 181 |
+
"es6.array.iterator": {
|
| 182 |
+
"chrome": "66",
|
| 183 |
+
"opera": "53",
|
| 184 |
+
"edge": "12",
|
| 185 |
+
"firefox": "60",
|
| 186 |
+
"safari": "9",
|
| 187 |
+
"node": "10",
|
| 188 |
+
"deno": "1",
|
| 189 |
+
"ios": "9",
|
| 190 |
+
"samsung": "9",
|
| 191 |
+
"rhino": "1.7.13",
|
| 192 |
+
"opera_mobile": "47",
|
| 193 |
+
"electron": "3.0"
|
| 194 |
+
},
|
| 195 |
+
"es6.array.last-index-of": {
|
| 196 |
+
"chrome": "5",
|
| 197 |
+
"opera": "10.10",
|
| 198 |
+
"edge": "12",
|
| 199 |
+
"firefox": "2",
|
| 200 |
+
"safari": "3.1",
|
| 201 |
+
"node": "0.4",
|
| 202 |
+
"deno": "1",
|
| 203 |
+
"ie": "9",
|
| 204 |
+
"android": "4",
|
| 205 |
+
"ios": "6",
|
| 206 |
+
"phantom": "1.9",
|
| 207 |
+
"samsung": "1",
|
| 208 |
+
"rhino": "1.7.13",
|
| 209 |
+
"opera_mobile": "10.1",
|
| 210 |
+
"electron": "0.20"
|
| 211 |
+
},
|
| 212 |
+
"es6.array.map": {
|
| 213 |
+
"chrome": "51",
|
| 214 |
+
"opera": "38",
|
| 215 |
+
"edge": "13",
|
| 216 |
+
"firefox": "48",
|
| 217 |
+
"safari": "10",
|
| 218 |
+
"node": "6.5",
|
| 219 |
+
"deno": "1",
|
| 220 |
+
"ios": "10",
|
| 221 |
+
"samsung": "5",
|
| 222 |
+
"opera_mobile": "41",
|
| 223 |
+
"electron": "1.2"
|
| 224 |
+
},
|
| 225 |
+
"es6.array.of": {
|
| 226 |
+
"chrome": "45",
|
| 227 |
+
"opera": "32",
|
| 228 |
+
"edge": "12",
|
| 229 |
+
"firefox": "25",
|
| 230 |
+
"safari": "9",
|
| 231 |
+
"node": "4",
|
| 232 |
+
"deno": "1",
|
| 233 |
+
"ios": "9",
|
| 234 |
+
"samsung": "5",
|
| 235 |
+
"rhino": "1.7.13",
|
| 236 |
+
"opera_mobile": "32",
|
| 237 |
+
"electron": "0.31"
|
| 238 |
+
},
|
| 239 |
+
"es6.array.reduce": {
|
| 240 |
+
"chrome": "5",
|
| 241 |
+
"opera": "10.50",
|
| 242 |
+
"edge": "12",
|
| 243 |
+
"firefox": "3",
|
| 244 |
+
"safari": "4",
|
| 245 |
+
"node": "0.4",
|
| 246 |
+
"deno": "1",
|
| 247 |
+
"ie": "9",
|
| 248 |
+
"android": "4",
|
| 249 |
+
"ios": "6",
|
| 250 |
+
"phantom": "1.9",
|
| 251 |
+
"samsung": "1",
|
| 252 |
+
"rhino": "1.7.13",
|
| 253 |
+
"opera_mobile": "10.1",
|
| 254 |
+
"electron": "0.20"
|
| 255 |
+
},
|
| 256 |
+
"es6.array.reduce-right": {
|
| 257 |
+
"chrome": "5",
|
| 258 |
+
"opera": "10.50",
|
| 259 |
+
"edge": "12",
|
| 260 |
+
"firefox": "3",
|
| 261 |
+
"safari": "4",
|
| 262 |
+
"node": "0.4",
|
| 263 |
+
"deno": "1",
|
| 264 |
+
"ie": "9",
|
| 265 |
+
"android": "4",
|
| 266 |
+
"ios": "6",
|
| 267 |
+
"phantom": "1.9",
|
| 268 |
+
"samsung": "1",
|
| 269 |
+
"rhino": "1.7.13",
|
| 270 |
+
"opera_mobile": "10.1",
|
| 271 |
+
"electron": "0.20"
|
| 272 |
+
},
|
| 273 |
+
"es6.array.slice": {
|
| 274 |
+
"chrome": "51",
|
| 275 |
+
"opera": "38",
|
| 276 |
+
"edge": "13",
|
| 277 |
+
"firefox": "48",
|
| 278 |
+
"safari": "10",
|
| 279 |
+
"node": "6.5",
|
| 280 |
+
"deno": "1",
|
| 281 |
+
"ios": "10",
|
| 282 |
+
"samsung": "5",
|
| 283 |
+
"opera_mobile": "41",
|
| 284 |
+
"electron": "1.2"
|
| 285 |
+
},
|
| 286 |
+
"es6.array.some": {
|
| 287 |
+
"chrome": "5",
|
| 288 |
+
"opera": "10.10",
|
| 289 |
+
"edge": "12",
|
| 290 |
+
"firefox": "2",
|
| 291 |
+
"safari": "3.1",
|
| 292 |
+
"node": "0.4",
|
| 293 |
+
"deno": "1",
|
| 294 |
+
"ie": "9",
|
| 295 |
+
"android": "4",
|
| 296 |
+
"ios": "6",
|
| 297 |
+
"phantom": "1.9",
|
| 298 |
+
"samsung": "1",
|
| 299 |
+
"rhino": "1.7.13",
|
| 300 |
+
"opera_mobile": "10.1",
|
| 301 |
+
"electron": "0.20"
|
| 302 |
+
},
|
| 303 |
+
"es6.array.sort": {
|
| 304 |
+
"chrome": "63",
|
| 305 |
+
"opera": "50",
|
| 306 |
+
"edge": "12",
|
| 307 |
+
"firefox": "5",
|
| 308 |
+
"safari": "12",
|
| 309 |
+
"node": "10",
|
| 310 |
+
"deno": "1",
|
| 311 |
+
"ie": "9",
|
| 312 |
+
"ios": "12",
|
| 313 |
+
"samsung": "8",
|
| 314 |
+
"rhino": "1.7.13",
|
| 315 |
+
"opera_mobile": "46",
|
| 316 |
+
"electron": "3.0"
|
| 317 |
+
},
|
| 318 |
+
"es6.array.species": {
|
| 319 |
+
"chrome": "51",
|
| 320 |
+
"opera": "38",
|
| 321 |
+
"edge": "13",
|
| 322 |
+
"firefox": "48",
|
| 323 |
+
"safari": "10",
|
| 324 |
+
"node": "6.5",
|
| 325 |
+
"deno": "1",
|
| 326 |
+
"ios": "10",
|
| 327 |
+
"samsung": "5",
|
| 328 |
+
"rhino": "1.7.15",
|
| 329 |
+
"opera_mobile": "41",
|
| 330 |
+
"electron": "1.2"
|
| 331 |
+
},
|
| 332 |
+
"es6.date.now": {
|
| 333 |
+
"chrome": "5",
|
| 334 |
+
"opera": "10.50",
|
| 335 |
+
"edge": "12",
|
| 336 |
+
"firefox": "2",
|
| 337 |
+
"safari": "4",
|
| 338 |
+
"node": "0.4",
|
| 339 |
+
"deno": "1",
|
| 340 |
+
"ie": "9",
|
| 341 |
+
"android": "4",
|
| 342 |
+
"ios": "6",
|
| 343 |
+
"phantom": "1.9",
|
| 344 |
+
"samsung": "1",
|
| 345 |
+
"rhino": "1.7.13",
|
| 346 |
+
"opera_mobile": "10.1",
|
| 347 |
+
"electron": "0.20"
|
| 348 |
+
},
|
| 349 |
+
"es6.date.to-iso-string": {
|
| 350 |
+
"chrome": "5",
|
| 351 |
+
"opera": "10.50",
|
| 352 |
+
"edge": "12",
|
| 353 |
+
"firefox": "3.5",
|
| 354 |
+
"safari": "4",
|
| 355 |
+
"node": "0.4",
|
| 356 |
+
"deno": "1",
|
| 357 |
+
"ie": "9",
|
| 358 |
+
"android": "4",
|
| 359 |
+
"ios": "6",
|
| 360 |
+
"phantom": "1.9",
|
| 361 |
+
"samsung": "1",
|
| 362 |
+
"rhino": "1.7.13",
|
| 363 |
+
"opera_mobile": "10.1",
|
| 364 |
+
"electron": "0.20"
|
| 365 |
+
},
|
| 366 |
+
"es6.date.to-json": {
|
| 367 |
+
"chrome": "5",
|
| 368 |
+
"opera": "12.10",
|
| 369 |
+
"edge": "12",
|
| 370 |
+
"firefox": "4",
|
| 371 |
+
"safari": "10",
|
| 372 |
+
"node": "0.4",
|
| 373 |
+
"deno": "1",
|
| 374 |
+
"ie": "9",
|
| 375 |
+
"android": "4",
|
| 376 |
+
"ios": "10",
|
| 377 |
+
"samsung": "1",
|
| 378 |
+
"rhino": "1.7.13",
|
| 379 |
+
"opera_mobile": "12.1",
|
| 380 |
+
"electron": "0.20"
|
| 381 |
+
},
|
| 382 |
+
"es6.date.to-primitive": {
|
| 383 |
+
"chrome": "47",
|
| 384 |
+
"opera": "34",
|
| 385 |
+
"edge": "15",
|
| 386 |
+
"firefox": "44",
|
| 387 |
+
"safari": "10",
|
| 388 |
+
"node": "6",
|
| 389 |
+
"deno": "1",
|
| 390 |
+
"ios": "10",
|
| 391 |
+
"samsung": "5",
|
| 392 |
+
"rhino": "1.8",
|
| 393 |
+
"opera_mobile": "34",
|
| 394 |
+
"electron": "0.36"
|
| 395 |
+
},
|
| 396 |
+
"es6.date.to-string": {
|
| 397 |
+
"chrome": "5",
|
| 398 |
+
"opera": "10.50",
|
| 399 |
+
"edge": "12",
|
| 400 |
+
"firefox": "2",
|
| 401 |
+
"safari": "3.1",
|
| 402 |
+
"node": "0.4",
|
| 403 |
+
"deno": "1",
|
| 404 |
+
"ie": "10",
|
| 405 |
+
"android": "4",
|
| 406 |
+
"ios": "6",
|
| 407 |
+
"phantom": "1.9",
|
| 408 |
+
"samsung": "1",
|
| 409 |
+
"rhino": "1.7.13",
|
| 410 |
+
"opera_mobile": "10.1",
|
| 411 |
+
"electron": "0.20"
|
| 412 |
+
},
|
| 413 |
+
"es6.function.bind": {
|
| 414 |
+
"chrome": "7",
|
| 415 |
+
"opera": "12",
|
| 416 |
+
"edge": "12",
|
| 417 |
+
"firefox": "4",
|
| 418 |
+
"safari": "5.1",
|
| 419 |
+
"node": "0.4",
|
| 420 |
+
"deno": "1",
|
| 421 |
+
"ie": "9",
|
| 422 |
+
"android": "4",
|
| 423 |
+
"ios": "6",
|
| 424 |
+
"phantom": "1.9",
|
| 425 |
+
"samsung": "1",
|
| 426 |
+
"rhino": "1.7.13",
|
| 427 |
+
"opera_mobile": "12",
|
| 428 |
+
"electron": "0.20"
|
| 429 |
+
},
|
| 430 |
+
"es6.function.has-instance": {
|
| 431 |
+
"chrome": "51",
|
| 432 |
+
"opera": "38",
|
| 433 |
+
"edge": "15",
|
| 434 |
+
"firefox": "50",
|
| 435 |
+
"safari": "10",
|
| 436 |
+
"node": "6.5",
|
| 437 |
+
"deno": "1",
|
| 438 |
+
"ios": "10",
|
| 439 |
+
"samsung": "5",
|
| 440 |
+
"opera_mobile": "41",
|
| 441 |
+
"electron": "1.2"
|
| 442 |
+
},
|
| 443 |
+
"es6.function.name": {
|
| 444 |
+
"chrome": "5",
|
| 445 |
+
"opera": "10.50",
|
| 446 |
+
"edge": "14",
|
| 447 |
+
"firefox": "2",
|
| 448 |
+
"safari": "4",
|
| 449 |
+
"node": "0.4",
|
| 450 |
+
"deno": "1",
|
| 451 |
+
"android": "4",
|
| 452 |
+
"ios": "6",
|
| 453 |
+
"phantom": "1.9",
|
| 454 |
+
"samsung": "1",
|
| 455 |
+
"rhino": "1.7.13",
|
| 456 |
+
"opera_mobile": "10.1",
|
| 457 |
+
"electron": "0.20"
|
| 458 |
+
},
|
| 459 |
+
"es6.map": {
|
| 460 |
+
"chrome": "51",
|
| 461 |
+
"opera": "38",
|
| 462 |
+
"edge": "15",
|
| 463 |
+
"firefox": "53",
|
| 464 |
+
"safari": "10",
|
| 465 |
+
"node": "6.5",
|
| 466 |
+
"deno": "1",
|
| 467 |
+
"ios": "10",
|
| 468 |
+
"samsung": "5",
|
| 469 |
+
"opera_mobile": "41",
|
| 470 |
+
"electron": "1.2"
|
| 471 |
+
},
|
| 472 |
+
"es6.math.acosh": {
|
| 473 |
+
"chrome": "38",
|
| 474 |
+
"opera": "25",
|
| 475 |
+
"edge": "12",
|
| 476 |
+
"firefox": "25",
|
| 477 |
+
"safari": "7.1",
|
| 478 |
+
"node": "0.12",
|
| 479 |
+
"deno": "1",
|
| 480 |
+
"ios": "8",
|
| 481 |
+
"samsung": "3",
|
| 482 |
+
"rhino": "1.7.13",
|
| 483 |
+
"opera_mobile": "25",
|
| 484 |
+
"electron": "0.20"
|
| 485 |
+
},
|
| 486 |
+
"es6.math.asinh": {
|
| 487 |
+
"chrome": "38",
|
| 488 |
+
"opera": "25",
|
| 489 |
+
"edge": "12",
|
| 490 |
+
"firefox": "25",
|
| 491 |
+
"safari": "7.1",
|
| 492 |
+
"node": "0.12",
|
| 493 |
+
"deno": "1",
|
| 494 |
+
"ios": "8",
|
| 495 |
+
"samsung": "3",
|
| 496 |
+
"rhino": "1.7.13",
|
| 497 |
+
"opera_mobile": "25",
|
| 498 |
+
"electron": "0.20"
|
| 499 |
+
},
|
| 500 |
+
"es6.math.atanh": {
|
| 501 |
+
"chrome": "38",
|
| 502 |
+
"opera": "25",
|
| 503 |
+
"edge": "12",
|
| 504 |
+
"firefox": "25",
|
| 505 |
+
"safari": "7.1",
|
| 506 |
+
"node": "0.12",
|
| 507 |
+
"deno": "1",
|
| 508 |
+
"ios": "8",
|
| 509 |
+
"samsung": "3",
|
| 510 |
+
"rhino": "1.7.13",
|
| 511 |
+
"opera_mobile": "25",
|
| 512 |
+
"electron": "0.20"
|
| 513 |
+
},
|
| 514 |
+
"es6.math.cbrt": {
|
| 515 |
+
"chrome": "38",
|
| 516 |
+
"opera": "25",
|
| 517 |
+
"edge": "12",
|
| 518 |
+
"firefox": "25",
|
| 519 |
+
"safari": "7.1",
|
| 520 |
+
"node": "0.12",
|
| 521 |
+
"deno": "1",
|
| 522 |
+
"ios": "8",
|
| 523 |
+
"samsung": "3",
|
| 524 |
+
"rhino": "1.7.13",
|
| 525 |
+
"opera_mobile": "25",
|
| 526 |
+
"electron": "0.20"
|
| 527 |
+
},
|
| 528 |
+
"es6.math.clz32": {
|
| 529 |
+
"chrome": "38",
|
| 530 |
+
"opera": "25",
|
| 531 |
+
"edge": "12",
|
| 532 |
+
"firefox": "31",
|
| 533 |
+
"safari": "9",
|
| 534 |
+
"node": "0.12",
|
| 535 |
+
"deno": "1",
|
| 536 |
+
"ios": "9",
|
| 537 |
+
"samsung": "3",
|
| 538 |
+
"rhino": "1.7.13",
|
| 539 |
+
"opera_mobile": "25",
|
| 540 |
+
"electron": "0.20"
|
| 541 |
+
},
|
| 542 |
+
"es6.math.cosh": {
|
| 543 |
+
"chrome": "38",
|
| 544 |
+
"opera": "25",
|
| 545 |
+
"edge": "12",
|
| 546 |
+
"firefox": "25",
|
| 547 |
+
"safari": "7.1",
|
| 548 |
+
"node": "0.12",
|
| 549 |
+
"deno": "1",
|
| 550 |
+
"ios": "8",
|
| 551 |
+
"samsung": "3",
|
| 552 |
+
"rhino": "1.7.13",
|
| 553 |
+
"opera_mobile": "25",
|
| 554 |
+
"electron": "0.20"
|
| 555 |
+
},
|
| 556 |
+
"es6.math.expm1": {
|
| 557 |
+
"chrome": "38",
|
| 558 |
+
"opera": "25",
|
| 559 |
+
"edge": "12",
|
| 560 |
+
"firefox": "25",
|
| 561 |
+
"safari": "7.1",
|
| 562 |
+
"node": "0.12",
|
| 563 |
+
"deno": "1",
|
| 564 |
+
"ios": "8",
|
| 565 |
+
"samsung": "3",
|
| 566 |
+
"rhino": "1.7.13",
|
| 567 |
+
"opera_mobile": "25",
|
| 568 |
+
"electron": "0.20"
|
| 569 |
+
},
|
| 570 |
+
"es6.math.fround": {
|
| 571 |
+
"chrome": "38",
|
| 572 |
+
"opera": "25",
|
| 573 |
+
"edge": "12",
|
| 574 |
+
"firefox": "26",
|
| 575 |
+
"safari": "7.1",
|
| 576 |
+
"node": "0.12",
|
| 577 |
+
"deno": "1",
|
| 578 |
+
"ios": "8",
|
| 579 |
+
"samsung": "3",
|
| 580 |
+
"rhino": "1.7.13",
|
| 581 |
+
"opera_mobile": "25",
|
| 582 |
+
"electron": "0.20"
|
| 583 |
+
},
|
| 584 |
+
"es6.math.hypot": {
|
| 585 |
+
"chrome": "38",
|
| 586 |
+
"opera": "25",
|
| 587 |
+
"edge": "12",
|
| 588 |
+
"firefox": "27",
|
| 589 |
+
"safari": "7.1",
|
| 590 |
+
"node": "0.12",
|
| 591 |
+
"deno": "1",
|
| 592 |
+
"ios": "8",
|
| 593 |
+
"samsung": "3",
|
| 594 |
+
"rhino": "1.7.13",
|
| 595 |
+
"opera_mobile": "25",
|
| 596 |
+
"electron": "0.20"
|
| 597 |
+
},
|
| 598 |
+
"es6.math.imul": {
|
| 599 |
+
"chrome": "30",
|
| 600 |
+
"opera": "17",
|
| 601 |
+
"edge": "12",
|
| 602 |
+
"firefox": "23",
|
| 603 |
+
"safari": "7",
|
| 604 |
+
"node": "0.12",
|
| 605 |
+
"deno": "1",
|
| 606 |
+
"android": "4.4",
|
| 607 |
+
"ios": "7",
|
| 608 |
+
"samsung": "2",
|
| 609 |
+
"rhino": "1.7.13",
|
| 610 |
+
"opera_mobile": "18",
|
| 611 |
+
"electron": "0.20"
|
| 612 |
+
},
|
| 613 |
+
"es6.math.log1p": {
|
| 614 |
+
"chrome": "38",
|
| 615 |
+
"opera": "25",
|
| 616 |
+
"edge": "12",
|
| 617 |
+
"firefox": "25",
|
| 618 |
+
"safari": "7.1",
|
| 619 |
+
"node": "0.12",
|
| 620 |
+
"deno": "1",
|
| 621 |
+
"ios": "8",
|
| 622 |
+
"samsung": "3",
|
| 623 |
+
"rhino": "1.7.13",
|
| 624 |
+
"opera_mobile": "25",
|
| 625 |
+
"electron": "0.20"
|
| 626 |
+
},
|
| 627 |
+
"es6.math.log10": {
|
| 628 |
+
"chrome": "38",
|
| 629 |
+
"opera": "25",
|
| 630 |
+
"edge": "12",
|
| 631 |
+
"firefox": "25",
|
| 632 |
+
"safari": "7.1",
|
| 633 |
+
"node": "0.12",
|
| 634 |
+
"deno": "1",
|
| 635 |
+
"ios": "8",
|
| 636 |
+
"samsung": "3",
|
| 637 |
+
"rhino": "1.7.13",
|
| 638 |
+
"opera_mobile": "25",
|
| 639 |
+
"electron": "0.20"
|
| 640 |
+
},
|
| 641 |
+
"es6.math.log2": {
|
| 642 |
+
"chrome": "38",
|
| 643 |
+
"opera": "25",
|
| 644 |
+
"edge": "12",
|
| 645 |
+
"firefox": "25",
|
| 646 |
+
"safari": "7.1",
|
| 647 |
+
"node": "0.12",
|
| 648 |
+
"deno": "1",
|
| 649 |
+
"ios": "8",
|
| 650 |
+
"samsung": "3",
|
| 651 |
+
"rhino": "1.7.13",
|
| 652 |
+
"opera_mobile": "25",
|
| 653 |
+
"electron": "0.20"
|
| 654 |
+
},
|
| 655 |
+
"es6.math.sign": {
|
| 656 |
+
"chrome": "38",
|
| 657 |
+
"opera": "25",
|
| 658 |
+
"edge": "12",
|
| 659 |
+
"firefox": "25",
|
| 660 |
+
"safari": "9",
|
| 661 |
+
"node": "0.12",
|
| 662 |
+
"deno": "1",
|
| 663 |
+
"ios": "9",
|
| 664 |
+
"samsung": "3",
|
| 665 |
+
"rhino": "1.7.13",
|
| 666 |
+
"opera_mobile": "25",
|
| 667 |
+
"electron": "0.20"
|
| 668 |
+
},
|
| 669 |
+
"es6.math.sinh": {
|
| 670 |
+
"chrome": "38",
|
| 671 |
+
"opera": "25",
|
| 672 |
+
"edge": "12",
|
| 673 |
+
"firefox": "25",
|
| 674 |
+
"safari": "7.1",
|
| 675 |
+
"node": "0.12",
|
| 676 |
+
"deno": "1",
|
| 677 |
+
"ios": "8",
|
| 678 |
+
"samsung": "3",
|
| 679 |
+
"rhino": "1.7.13",
|
| 680 |
+
"opera_mobile": "25",
|
| 681 |
+
"electron": "0.20"
|
| 682 |
+
},
|
| 683 |
+
"es6.math.tanh": {
|
| 684 |
+
"chrome": "38",
|
| 685 |
+
"opera": "25",
|
| 686 |
+
"edge": "12",
|
| 687 |
+
"firefox": "25",
|
| 688 |
+
"safari": "7.1",
|
| 689 |
+
"node": "0.12",
|
| 690 |
+
"deno": "1",
|
| 691 |
+
"ios": "8",
|
| 692 |
+
"samsung": "3",
|
| 693 |
+
"rhino": "1.7.13",
|
| 694 |
+
"opera_mobile": "25",
|
| 695 |
+
"electron": "0.20"
|
| 696 |
+
},
|
| 697 |
+
"es6.math.trunc": {
|
| 698 |
+
"chrome": "38",
|
| 699 |
+
"opera": "25",
|
| 700 |
+
"edge": "12",
|
| 701 |
+
"firefox": "25",
|
| 702 |
+
"safari": "7.1",
|
| 703 |
+
"node": "0.12",
|
| 704 |
+
"deno": "1",
|
| 705 |
+
"ios": "8",
|
| 706 |
+
"samsung": "3",
|
| 707 |
+
"rhino": "1.7.13",
|
| 708 |
+
"opera_mobile": "25",
|
| 709 |
+
"electron": "0.20"
|
| 710 |
+
},
|
| 711 |
+
"es6.number.constructor": {
|
| 712 |
+
"chrome": "41",
|
| 713 |
+
"opera": "28",
|
| 714 |
+
"edge": "12",
|
| 715 |
+
"firefox": "36",
|
| 716 |
+
"safari": "9",
|
| 717 |
+
"node": "4",
|
| 718 |
+
"deno": "1",
|
| 719 |
+
"ios": "9",
|
| 720 |
+
"samsung": "3.4",
|
| 721 |
+
"rhino": "1.7.13",
|
| 722 |
+
"opera_mobile": "28",
|
| 723 |
+
"electron": "0.21"
|
| 724 |
+
},
|
| 725 |
+
"es6.number.epsilon": {
|
| 726 |
+
"chrome": "34",
|
| 727 |
+
"opera": "21",
|
| 728 |
+
"edge": "12",
|
| 729 |
+
"firefox": "25",
|
| 730 |
+
"safari": "9",
|
| 731 |
+
"node": "0.12",
|
| 732 |
+
"deno": "1",
|
| 733 |
+
"ios": "9",
|
| 734 |
+
"samsung": "2",
|
| 735 |
+
"rhino": "1.7.14",
|
| 736 |
+
"opera_mobile": "21",
|
| 737 |
+
"electron": "0.20"
|
| 738 |
+
},
|
| 739 |
+
"es6.number.is-finite": {
|
| 740 |
+
"chrome": "19",
|
| 741 |
+
"opera": "15",
|
| 742 |
+
"edge": "12",
|
| 743 |
+
"firefox": "16",
|
| 744 |
+
"safari": "9",
|
| 745 |
+
"node": "0.8",
|
| 746 |
+
"deno": "1",
|
| 747 |
+
"android": "4.1",
|
| 748 |
+
"ios": "9",
|
| 749 |
+
"samsung": "1.5",
|
| 750 |
+
"rhino": "1.7.13",
|
| 751 |
+
"opera_mobile": "14",
|
| 752 |
+
"electron": "0.20"
|
| 753 |
+
},
|
| 754 |
+
"es6.number.is-integer": {
|
| 755 |
+
"chrome": "34",
|
| 756 |
+
"opera": "21",
|
| 757 |
+
"edge": "12",
|
| 758 |
+
"firefox": "16",
|
| 759 |
+
"safari": "9",
|
| 760 |
+
"node": "0.12",
|
| 761 |
+
"deno": "1",
|
| 762 |
+
"ios": "9",
|
| 763 |
+
"samsung": "2",
|
| 764 |
+
"rhino": "1.7.13",
|
| 765 |
+
"opera_mobile": "21",
|
| 766 |
+
"electron": "0.20"
|
| 767 |
+
},
|
| 768 |
+
"es6.number.is-nan": {
|
| 769 |
+
"chrome": "19",
|
| 770 |
+
"opera": "15",
|
| 771 |
+
"edge": "12",
|
| 772 |
+
"firefox": "15",
|
| 773 |
+
"safari": "9",
|
| 774 |
+
"node": "0.8",
|
| 775 |
+
"deno": "1",
|
| 776 |
+
"android": "4.1",
|
| 777 |
+
"ios": "9",
|
| 778 |
+
"samsung": "1.5",
|
| 779 |
+
"rhino": "1.7.13",
|
| 780 |
+
"opera_mobile": "14",
|
| 781 |
+
"electron": "0.20"
|
| 782 |
+
},
|
| 783 |
+
"es6.number.is-safe-integer": {
|
| 784 |
+
"chrome": "34",
|
| 785 |
+
"opera": "21",
|
| 786 |
+
"edge": "12",
|
| 787 |
+
"firefox": "32",
|
| 788 |
+
"safari": "9",
|
| 789 |
+
"node": "0.12",
|
| 790 |
+
"deno": "1",
|
| 791 |
+
"ios": "9",
|
| 792 |
+
"samsung": "2",
|
| 793 |
+
"rhino": "1.7.13",
|
| 794 |
+
"opera_mobile": "21",
|
| 795 |
+
"electron": "0.20"
|
| 796 |
+
},
|
| 797 |
+
"es6.number.max-safe-integer": {
|
| 798 |
+
"chrome": "34",
|
| 799 |
+
"opera": "21",
|
| 800 |
+
"edge": "12",
|
| 801 |
+
"firefox": "31",
|
| 802 |
+
"safari": "9",
|
| 803 |
+
"node": "0.12",
|
| 804 |
+
"deno": "1",
|
| 805 |
+
"ios": "9",
|
| 806 |
+
"samsung": "2",
|
| 807 |
+
"rhino": "1.7.13",
|
| 808 |
+
"opera_mobile": "21",
|
| 809 |
+
"electron": "0.20"
|
| 810 |
+
},
|
| 811 |
+
"es6.number.min-safe-integer": {
|
| 812 |
+
"chrome": "34",
|
| 813 |
+
"opera": "21",
|
| 814 |
+
"edge": "12",
|
| 815 |
+
"firefox": "31",
|
| 816 |
+
"safari": "9",
|
| 817 |
+
"node": "0.12",
|
| 818 |
+
"deno": "1",
|
| 819 |
+
"ios": "9",
|
| 820 |
+
"samsung": "2",
|
| 821 |
+
"rhino": "1.7.13",
|
| 822 |
+
"opera_mobile": "21",
|
| 823 |
+
"electron": "0.20"
|
| 824 |
+
},
|
| 825 |
+
"es6.number.parse-float": {
|
| 826 |
+
"chrome": "34",
|
| 827 |
+
"opera": "21",
|
| 828 |
+
"edge": "12",
|
| 829 |
+
"firefox": "25",
|
| 830 |
+
"safari": "9",
|
| 831 |
+
"node": "0.12",
|
| 832 |
+
"deno": "1",
|
| 833 |
+
"ios": "9",
|
| 834 |
+
"samsung": "2",
|
| 835 |
+
"rhino": "1.7.14",
|
| 836 |
+
"opera_mobile": "21",
|
| 837 |
+
"electron": "0.20"
|
| 838 |
+
},
|
| 839 |
+
"es6.number.parse-int": {
|
| 840 |
+
"chrome": "34",
|
| 841 |
+
"opera": "21",
|
| 842 |
+
"edge": "12",
|
| 843 |
+
"firefox": "25",
|
| 844 |
+
"safari": "9",
|
| 845 |
+
"node": "0.12",
|
| 846 |
+
"deno": "1",
|
| 847 |
+
"ios": "9",
|
| 848 |
+
"samsung": "2",
|
| 849 |
+
"rhino": "1.7.14",
|
| 850 |
+
"opera_mobile": "21",
|
| 851 |
+
"electron": "0.20"
|
| 852 |
+
},
|
| 853 |
+
"es6.object.assign": {
|
| 854 |
+
"chrome": "49",
|
| 855 |
+
"opera": "36",
|
| 856 |
+
"edge": "13",
|
| 857 |
+
"firefox": "36",
|
| 858 |
+
"safari": "10",
|
| 859 |
+
"node": "6",
|
| 860 |
+
"deno": "1",
|
| 861 |
+
"ios": "10",
|
| 862 |
+
"samsung": "5",
|
| 863 |
+
"rhino": "1.8",
|
| 864 |
+
"opera_mobile": "36",
|
| 865 |
+
"electron": "0.37"
|
| 866 |
+
},
|
| 867 |
+
"es6.object.create": {
|
| 868 |
+
"chrome": "5",
|
| 869 |
+
"opera": "12",
|
| 870 |
+
"edge": "12",
|
| 871 |
+
"firefox": "4",
|
| 872 |
+
"safari": "4",
|
| 873 |
+
"node": "0.4",
|
| 874 |
+
"deno": "1",
|
| 875 |
+
"ie": "9",
|
| 876 |
+
"android": "4",
|
| 877 |
+
"ios": "6",
|
| 878 |
+
"phantom": "1.9",
|
| 879 |
+
"samsung": "1",
|
| 880 |
+
"rhino": "1.7.13",
|
| 881 |
+
"opera_mobile": "12",
|
| 882 |
+
"electron": "0.20"
|
| 883 |
+
},
|
| 884 |
+
"es7.object.define-getter": {
|
| 885 |
+
"chrome": "62",
|
| 886 |
+
"opera": "49",
|
| 887 |
+
"edge": "16",
|
| 888 |
+
"firefox": "48",
|
| 889 |
+
"safari": "9",
|
| 890 |
+
"node": "8.10",
|
| 891 |
+
"deno": "1",
|
| 892 |
+
"ios": "9",
|
| 893 |
+
"samsung": "8",
|
| 894 |
+
"opera_mobile": "46",
|
| 895 |
+
"electron": "3.0"
|
| 896 |
+
},
|
| 897 |
+
"es7.object.define-setter": {
|
| 898 |
+
"chrome": "62",
|
| 899 |
+
"opera": "49",
|
| 900 |
+
"edge": "16",
|
| 901 |
+
"firefox": "48",
|
| 902 |
+
"safari": "9",
|
| 903 |
+
"node": "8.10",
|
| 904 |
+
"deno": "1",
|
| 905 |
+
"ios": "9",
|
| 906 |
+
"samsung": "8",
|
| 907 |
+
"opera_mobile": "46",
|
| 908 |
+
"electron": "3.0"
|
| 909 |
+
},
|
| 910 |
+
"es6.object.define-property": {
|
| 911 |
+
"chrome": "5",
|
| 912 |
+
"opera": "12",
|
| 913 |
+
"edge": "12",
|
| 914 |
+
"firefox": "4",
|
| 915 |
+
"safari": "5.1",
|
| 916 |
+
"node": "0.4",
|
| 917 |
+
"deno": "1",
|
| 918 |
+
"ie": "9",
|
| 919 |
+
"android": "4",
|
| 920 |
+
"ios": "6",
|
| 921 |
+
"phantom": "1.9",
|
| 922 |
+
"samsung": "1",
|
| 923 |
+
"rhino": "1.7.13",
|
| 924 |
+
"opera_mobile": "12",
|
| 925 |
+
"electron": "0.20"
|
| 926 |
+
},
|
| 927 |
+
"es6.object.define-properties": {
|
| 928 |
+
"chrome": "5",
|
| 929 |
+
"opera": "12",
|
| 930 |
+
"edge": "12",
|
| 931 |
+
"firefox": "4",
|
| 932 |
+
"safari": "4",
|
| 933 |
+
"node": "0.4",
|
| 934 |
+
"deno": "1",
|
| 935 |
+
"ie": "9",
|
| 936 |
+
"android": "4",
|
| 937 |
+
"ios": "6",
|
| 938 |
+
"phantom": "1.9",
|
| 939 |
+
"samsung": "1",
|
| 940 |
+
"rhino": "1.7.13",
|
| 941 |
+
"opera_mobile": "12",
|
| 942 |
+
"electron": "0.20"
|
| 943 |
+
},
|
| 944 |
+
"es7.object.entries": {
|
| 945 |
+
"chrome": "54",
|
| 946 |
+
"opera": "41",
|
| 947 |
+
"edge": "14",
|
| 948 |
+
"firefox": "47",
|
| 949 |
+
"safari": "10.1",
|
| 950 |
+
"node": "7",
|
| 951 |
+
"deno": "1",
|
| 952 |
+
"ios": "10.3",
|
| 953 |
+
"samsung": "6",
|
| 954 |
+
"rhino": "1.7.14",
|
| 955 |
+
"opera_mobile": "41",
|
| 956 |
+
"electron": "1.4"
|
| 957 |
+
},
|
| 958 |
+
"es6.object.freeze": {
|
| 959 |
+
"chrome": "44",
|
| 960 |
+
"opera": "31",
|
| 961 |
+
"edge": "12",
|
| 962 |
+
"firefox": "35",
|
| 963 |
+
"safari": "9",
|
| 964 |
+
"node": "4",
|
| 965 |
+
"deno": "1",
|
| 966 |
+
"ios": "9",
|
| 967 |
+
"samsung": "4",
|
| 968 |
+
"rhino": "1.7.13",
|
| 969 |
+
"opera_mobile": "32",
|
| 970 |
+
"electron": "0.30"
|
| 971 |
+
},
|
| 972 |
+
"es6.object.get-own-property-descriptor": {
|
| 973 |
+
"chrome": "44",
|
| 974 |
+
"opera": "31",
|
| 975 |
+
"edge": "12",
|
| 976 |
+
"firefox": "35",
|
| 977 |
+
"safari": "9",
|
| 978 |
+
"node": "4",
|
| 979 |
+
"deno": "1",
|
| 980 |
+
"ios": "9",
|
| 981 |
+
"samsung": "4",
|
| 982 |
+
"rhino": "1.7.13",
|
| 983 |
+
"opera_mobile": "32",
|
| 984 |
+
"electron": "0.30"
|
| 985 |
+
},
|
| 986 |
+
"es7.object.get-own-property-descriptors": {
|
| 987 |
+
"chrome": "54",
|
| 988 |
+
"opera": "41",
|
| 989 |
+
"edge": "15",
|
| 990 |
+
"firefox": "50",
|
| 991 |
+
"safari": "10.1",
|
| 992 |
+
"node": "7",
|
| 993 |
+
"deno": "1",
|
| 994 |
+
"ios": "10.3",
|
| 995 |
+
"samsung": "6",
|
| 996 |
+
"rhino": "1.8",
|
| 997 |
+
"opera_mobile": "41",
|
| 998 |
+
"electron": "1.4"
|
| 999 |
+
},
|
| 1000 |
+
"es6.object.get-own-property-names": {
|
| 1001 |
+
"chrome": "40",
|
| 1002 |
+
"opera": "27",
|
| 1003 |
+
"edge": "12",
|
| 1004 |
+
"firefox": "33",
|
| 1005 |
+
"safari": "9",
|
| 1006 |
+
"node": "4",
|
| 1007 |
+
"deno": "1",
|
| 1008 |
+
"ios": "9",
|
| 1009 |
+
"samsung": "3.4",
|
| 1010 |
+
"rhino": "1.7.13",
|
| 1011 |
+
"opera_mobile": "27",
|
| 1012 |
+
"electron": "0.21"
|
| 1013 |
+
},
|
| 1014 |
+
"es6.object.get-prototype-of": {
|
| 1015 |
+
"chrome": "44",
|
| 1016 |
+
"opera": "31",
|
| 1017 |
+
"edge": "12",
|
| 1018 |
+
"firefox": "35",
|
| 1019 |
+
"safari": "9",
|
| 1020 |
+
"node": "4",
|
| 1021 |
+
"deno": "1",
|
| 1022 |
+
"ios": "9",
|
| 1023 |
+
"samsung": "4",
|
| 1024 |
+
"rhino": "1.7.13",
|
| 1025 |
+
"opera_mobile": "32",
|
| 1026 |
+
"electron": "0.30"
|
| 1027 |
+
},
|
| 1028 |
+
"es7.object.lookup-getter": {
|
| 1029 |
+
"chrome": "62",
|
| 1030 |
+
"opera": "49",
|
| 1031 |
+
"edge": "79",
|
| 1032 |
+
"firefox": "36",
|
| 1033 |
+
"safari": "9",
|
| 1034 |
+
"node": "8.10",
|
| 1035 |
+
"deno": "1",
|
| 1036 |
+
"ios": "9",
|
| 1037 |
+
"samsung": "8",
|
| 1038 |
+
"opera_mobile": "46",
|
| 1039 |
+
"electron": "3.0"
|
| 1040 |
+
},
|
| 1041 |
+
"es7.object.lookup-setter": {
|
| 1042 |
+
"chrome": "62",
|
| 1043 |
+
"opera": "49",
|
| 1044 |
+
"edge": "79",
|
| 1045 |
+
"firefox": "36",
|
| 1046 |
+
"safari": "9",
|
| 1047 |
+
"node": "8.10",
|
| 1048 |
+
"deno": "1",
|
| 1049 |
+
"ios": "9",
|
| 1050 |
+
"samsung": "8",
|
| 1051 |
+
"opera_mobile": "46",
|
| 1052 |
+
"electron": "3.0"
|
| 1053 |
+
},
|
| 1054 |
+
"es6.object.prevent-extensions": {
|
| 1055 |
+
"chrome": "44",
|
| 1056 |
+
"opera": "31",
|
| 1057 |
+
"edge": "12",
|
| 1058 |
+
"firefox": "35",
|
| 1059 |
+
"safari": "9",
|
| 1060 |
+
"node": "4",
|
| 1061 |
+
"deno": "1",
|
| 1062 |
+
"ios": "9",
|
| 1063 |
+
"samsung": "4",
|
| 1064 |
+
"rhino": "1.7.13",
|
| 1065 |
+
"opera_mobile": "32",
|
| 1066 |
+
"electron": "0.30"
|
| 1067 |
+
},
|
| 1068 |
+
"es6.object.to-string": {
|
| 1069 |
+
"chrome": "57",
|
| 1070 |
+
"opera": "44",
|
| 1071 |
+
"edge": "15",
|
| 1072 |
+
"firefox": "51",
|
| 1073 |
+
"safari": "10",
|
| 1074 |
+
"node": "8",
|
| 1075 |
+
"deno": "1",
|
| 1076 |
+
"ios": "10",
|
| 1077 |
+
"samsung": "7",
|
| 1078 |
+
"opera_mobile": "43",
|
| 1079 |
+
"electron": "1.7"
|
| 1080 |
+
},
|
| 1081 |
+
"es6.object.is": {
|
| 1082 |
+
"chrome": "19",
|
| 1083 |
+
"opera": "15",
|
| 1084 |
+
"edge": "12",
|
| 1085 |
+
"firefox": "22",
|
| 1086 |
+
"safari": "9",
|
| 1087 |
+
"node": "0.8",
|
| 1088 |
+
"deno": "1",
|
| 1089 |
+
"android": "4.1",
|
| 1090 |
+
"ios": "9",
|
| 1091 |
+
"samsung": "1.5",
|
| 1092 |
+
"rhino": "1.7.13",
|
| 1093 |
+
"opera_mobile": "14",
|
| 1094 |
+
"electron": "0.20"
|
| 1095 |
+
},
|
| 1096 |
+
"es6.object.is-frozen": {
|
| 1097 |
+
"chrome": "44",
|
| 1098 |
+
"opera": "31",
|
| 1099 |
+
"edge": "12",
|
| 1100 |
+
"firefox": "35",
|
| 1101 |
+
"safari": "9",
|
| 1102 |
+
"node": "4",
|
| 1103 |
+
"deno": "1",
|
| 1104 |
+
"ios": "9",
|
| 1105 |
+
"samsung": "4",
|
| 1106 |
+
"rhino": "1.7.13",
|
| 1107 |
+
"opera_mobile": "32",
|
| 1108 |
+
"electron": "0.30"
|
| 1109 |
+
},
|
| 1110 |
+
"es6.object.is-sealed": {
|
| 1111 |
+
"chrome": "44",
|
| 1112 |
+
"opera": "31",
|
| 1113 |
+
"edge": "12",
|
| 1114 |
+
"firefox": "35",
|
| 1115 |
+
"safari": "9",
|
| 1116 |
+
"node": "4",
|
| 1117 |
+
"deno": "1",
|
| 1118 |
+
"ios": "9",
|
| 1119 |
+
"samsung": "4",
|
| 1120 |
+
"rhino": "1.7.13",
|
| 1121 |
+
"opera_mobile": "32",
|
| 1122 |
+
"electron": "0.30"
|
| 1123 |
+
},
|
| 1124 |
+
"es6.object.is-extensible": {
|
| 1125 |
+
"chrome": "44",
|
| 1126 |
+
"opera": "31",
|
| 1127 |
+
"edge": "12",
|
| 1128 |
+
"firefox": "35",
|
| 1129 |
+
"safari": "9",
|
| 1130 |
+
"node": "4",
|
| 1131 |
+
"deno": "1",
|
| 1132 |
+
"ios": "9",
|
| 1133 |
+
"samsung": "4",
|
| 1134 |
+
"rhino": "1.7.13",
|
| 1135 |
+
"opera_mobile": "32",
|
| 1136 |
+
"electron": "0.30"
|
| 1137 |
+
},
|
| 1138 |
+
"es6.object.keys": {
|
| 1139 |
+
"chrome": "40",
|
| 1140 |
+
"opera": "27",
|
| 1141 |
+
"edge": "12",
|
| 1142 |
+
"firefox": "35",
|
| 1143 |
+
"safari": "9",
|
| 1144 |
+
"node": "4",
|
| 1145 |
+
"deno": "1",
|
| 1146 |
+
"ios": "9",
|
| 1147 |
+
"samsung": "3.4",
|
| 1148 |
+
"rhino": "1.7.13",
|
| 1149 |
+
"opera_mobile": "27",
|
| 1150 |
+
"electron": "0.21"
|
| 1151 |
+
},
|
| 1152 |
+
"es6.object.seal": {
|
| 1153 |
+
"chrome": "44",
|
| 1154 |
+
"opera": "31",
|
| 1155 |
+
"edge": "12",
|
| 1156 |
+
"firefox": "35",
|
| 1157 |
+
"safari": "9",
|
| 1158 |
+
"node": "4",
|
| 1159 |
+
"deno": "1",
|
| 1160 |
+
"ios": "9",
|
| 1161 |
+
"samsung": "4",
|
| 1162 |
+
"rhino": "1.7.13",
|
| 1163 |
+
"opera_mobile": "32",
|
| 1164 |
+
"electron": "0.30"
|
| 1165 |
+
},
|
| 1166 |
+
"es6.object.set-prototype-of": {
|
| 1167 |
+
"chrome": "34",
|
| 1168 |
+
"opera": "21",
|
| 1169 |
+
"edge": "12",
|
| 1170 |
+
"firefox": "31",
|
| 1171 |
+
"safari": "9",
|
| 1172 |
+
"node": "0.12",
|
| 1173 |
+
"deno": "1",
|
| 1174 |
+
"ie": "11",
|
| 1175 |
+
"ios": "9",
|
| 1176 |
+
"samsung": "2",
|
| 1177 |
+
"rhino": "1.7.13",
|
| 1178 |
+
"opera_mobile": "21",
|
| 1179 |
+
"electron": "0.20"
|
| 1180 |
+
},
|
| 1181 |
+
"es7.object.values": {
|
| 1182 |
+
"chrome": "54",
|
| 1183 |
+
"opera": "41",
|
| 1184 |
+
"edge": "14",
|
| 1185 |
+
"firefox": "47",
|
| 1186 |
+
"safari": "10.1",
|
| 1187 |
+
"node": "7",
|
| 1188 |
+
"deno": "1",
|
| 1189 |
+
"ios": "10.3",
|
| 1190 |
+
"samsung": "6",
|
| 1191 |
+
"rhino": "1.7.14",
|
| 1192 |
+
"opera_mobile": "41",
|
| 1193 |
+
"electron": "1.4"
|
| 1194 |
+
},
|
| 1195 |
+
"es6.promise": {
|
| 1196 |
+
"chrome": "51",
|
| 1197 |
+
"opera": "38",
|
| 1198 |
+
"edge": "14",
|
| 1199 |
+
"firefox": "45",
|
| 1200 |
+
"safari": "10",
|
| 1201 |
+
"node": "6.5",
|
| 1202 |
+
"deno": "1",
|
| 1203 |
+
"ios": "10",
|
| 1204 |
+
"samsung": "5",
|
| 1205 |
+
"rhino": "1.7.15",
|
| 1206 |
+
"opera_mobile": "41",
|
| 1207 |
+
"electron": "1.2"
|
| 1208 |
+
},
|
| 1209 |
+
"es7.promise.finally": {
|
| 1210 |
+
"chrome": "63",
|
| 1211 |
+
"opera": "50",
|
| 1212 |
+
"edge": "18",
|
| 1213 |
+
"firefox": "58",
|
| 1214 |
+
"safari": "11.1",
|
| 1215 |
+
"node": "10",
|
| 1216 |
+
"deno": "1",
|
| 1217 |
+
"ios": "11.3",
|
| 1218 |
+
"samsung": "8",
|
| 1219 |
+
"rhino": "1.7.15",
|
| 1220 |
+
"opera_mobile": "46",
|
| 1221 |
+
"electron": "3.0"
|
| 1222 |
+
},
|
| 1223 |
+
"es6.reflect.apply": {
|
| 1224 |
+
"chrome": "49",
|
| 1225 |
+
"opera": "36",
|
| 1226 |
+
"edge": "12",
|
| 1227 |
+
"firefox": "42",
|
| 1228 |
+
"safari": "10",
|
| 1229 |
+
"node": "6",
|
| 1230 |
+
"deno": "1",
|
| 1231 |
+
"ios": "10",
|
| 1232 |
+
"samsung": "5",
|
| 1233 |
+
"rhino": "1.8",
|
| 1234 |
+
"opera_mobile": "36",
|
| 1235 |
+
"electron": "0.37"
|
| 1236 |
+
},
|
| 1237 |
+
"es6.reflect.construct": {
|
| 1238 |
+
"chrome": "49",
|
| 1239 |
+
"opera": "36",
|
| 1240 |
+
"edge": "13",
|
| 1241 |
+
"firefox": "49",
|
| 1242 |
+
"safari": "10",
|
| 1243 |
+
"node": "6",
|
| 1244 |
+
"deno": "1",
|
| 1245 |
+
"ios": "10",
|
| 1246 |
+
"samsung": "5",
|
| 1247 |
+
"opera_mobile": "36",
|
| 1248 |
+
"electron": "0.37"
|
| 1249 |
+
},
|
| 1250 |
+
"es6.reflect.define-property": {
|
| 1251 |
+
"chrome": "49",
|
| 1252 |
+
"opera": "36",
|
| 1253 |
+
"edge": "13",
|
| 1254 |
+
"firefox": "42",
|
| 1255 |
+
"safari": "10",
|
| 1256 |
+
"node": "6",
|
| 1257 |
+
"deno": "1",
|
| 1258 |
+
"ios": "10",
|
| 1259 |
+
"samsung": "5",
|
| 1260 |
+
"rhino": "1.8",
|
| 1261 |
+
"opera_mobile": "36",
|
| 1262 |
+
"electron": "0.37"
|
| 1263 |
+
},
|
| 1264 |
+
"es6.reflect.delete-property": {
|
| 1265 |
+
"chrome": "49",
|
| 1266 |
+
"opera": "36",
|
| 1267 |
+
"edge": "12",
|
| 1268 |
+
"firefox": "42",
|
| 1269 |
+
"safari": "10",
|
| 1270 |
+
"node": "6",
|
| 1271 |
+
"deno": "1",
|
| 1272 |
+
"ios": "10",
|
| 1273 |
+
"samsung": "5",
|
| 1274 |
+
"rhino": "1.8",
|
| 1275 |
+
"opera_mobile": "36",
|
| 1276 |
+
"electron": "0.37"
|
| 1277 |
+
},
|
| 1278 |
+
"es6.reflect.get": {
|
| 1279 |
+
"chrome": "49",
|
| 1280 |
+
"opera": "36",
|
| 1281 |
+
"edge": "12",
|
| 1282 |
+
"firefox": "42",
|
| 1283 |
+
"safari": "10",
|
| 1284 |
+
"node": "6",
|
| 1285 |
+
"deno": "1",
|
| 1286 |
+
"ios": "10",
|
| 1287 |
+
"samsung": "5",
|
| 1288 |
+
"rhino": "1.8",
|
| 1289 |
+
"opera_mobile": "36",
|
| 1290 |
+
"electron": "0.37"
|
| 1291 |
+
},
|
| 1292 |
+
"es6.reflect.get-own-property-descriptor": {
|
| 1293 |
+
"chrome": "49",
|
| 1294 |
+
"opera": "36",
|
| 1295 |
+
"edge": "12",
|
| 1296 |
+
"firefox": "42",
|
| 1297 |
+
"safari": "10",
|
| 1298 |
+
"node": "6",
|
| 1299 |
+
"deno": "1",
|
| 1300 |
+
"ios": "10",
|
| 1301 |
+
"samsung": "5",
|
| 1302 |
+
"rhino": "1.8",
|
| 1303 |
+
"opera_mobile": "36",
|
| 1304 |
+
"electron": "0.37"
|
| 1305 |
+
},
|
| 1306 |
+
"es6.reflect.get-prototype-of": {
|
| 1307 |
+
"chrome": "49",
|
| 1308 |
+
"opera": "36",
|
| 1309 |
+
"edge": "12",
|
| 1310 |
+
"firefox": "42",
|
| 1311 |
+
"safari": "10",
|
| 1312 |
+
"node": "6",
|
| 1313 |
+
"deno": "1",
|
| 1314 |
+
"ios": "10",
|
| 1315 |
+
"samsung": "5",
|
| 1316 |
+
"rhino": "1.8",
|
| 1317 |
+
"opera_mobile": "36",
|
| 1318 |
+
"electron": "0.37"
|
| 1319 |
+
},
|
| 1320 |
+
"es6.reflect.has": {
|
| 1321 |
+
"chrome": "49",
|
| 1322 |
+
"opera": "36",
|
| 1323 |
+
"edge": "12",
|
| 1324 |
+
"firefox": "42",
|
| 1325 |
+
"safari": "10",
|
| 1326 |
+
"node": "6",
|
| 1327 |
+
"deno": "1",
|
| 1328 |
+
"ios": "10",
|
| 1329 |
+
"samsung": "5",
|
| 1330 |
+
"rhino": "1.8",
|
| 1331 |
+
"opera_mobile": "36",
|
| 1332 |
+
"electron": "0.37"
|
| 1333 |
+
},
|
| 1334 |
+
"es6.reflect.is-extensible": {
|
| 1335 |
+
"chrome": "49",
|
| 1336 |
+
"opera": "36",
|
| 1337 |
+
"edge": "12",
|
| 1338 |
+
"firefox": "42",
|
| 1339 |
+
"safari": "10",
|
| 1340 |
+
"node": "6",
|
| 1341 |
+
"deno": "1",
|
| 1342 |
+
"ios": "10",
|
| 1343 |
+
"samsung": "5",
|
| 1344 |
+
"rhino": "1.8",
|
| 1345 |
+
"opera_mobile": "36",
|
| 1346 |
+
"electron": "0.37"
|
| 1347 |
+
},
|
| 1348 |
+
"es6.reflect.own-keys": {
|
| 1349 |
+
"chrome": "49",
|
| 1350 |
+
"opera": "36",
|
| 1351 |
+
"edge": "12",
|
| 1352 |
+
"firefox": "42",
|
| 1353 |
+
"safari": "10",
|
| 1354 |
+
"node": "6",
|
| 1355 |
+
"deno": "1",
|
| 1356 |
+
"ios": "10",
|
| 1357 |
+
"samsung": "5",
|
| 1358 |
+
"rhino": "1.8",
|
| 1359 |
+
"opera_mobile": "36",
|
| 1360 |
+
"electron": "0.37"
|
| 1361 |
+
},
|
| 1362 |
+
"es6.reflect.prevent-extensions": {
|
| 1363 |
+
"chrome": "49",
|
| 1364 |
+
"opera": "36",
|
| 1365 |
+
"edge": "12",
|
| 1366 |
+
"firefox": "42",
|
| 1367 |
+
"safari": "10",
|
| 1368 |
+
"node": "6",
|
| 1369 |
+
"deno": "1",
|
| 1370 |
+
"ios": "10",
|
| 1371 |
+
"samsung": "5",
|
| 1372 |
+
"rhino": "1.8",
|
| 1373 |
+
"opera_mobile": "36",
|
| 1374 |
+
"electron": "0.37"
|
| 1375 |
+
},
|
| 1376 |
+
"es6.reflect.set": {
|
| 1377 |
+
"chrome": "49",
|
| 1378 |
+
"opera": "36",
|
| 1379 |
+
"edge": "12",
|
| 1380 |
+
"firefox": "42",
|
| 1381 |
+
"safari": "10",
|
| 1382 |
+
"node": "6",
|
| 1383 |
+
"deno": "1",
|
| 1384 |
+
"ios": "10",
|
| 1385 |
+
"samsung": "5",
|
| 1386 |
+
"rhino": "1.8",
|
| 1387 |
+
"opera_mobile": "36",
|
| 1388 |
+
"electron": "0.37"
|
| 1389 |
+
},
|
| 1390 |
+
"es6.reflect.set-prototype-of": {
|
| 1391 |
+
"chrome": "49",
|
| 1392 |
+
"opera": "36",
|
| 1393 |
+
"edge": "12",
|
| 1394 |
+
"firefox": "42",
|
| 1395 |
+
"safari": "10",
|
| 1396 |
+
"node": "6",
|
| 1397 |
+
"deno": "1",
|
| 1398 |
+
"ios": "10",
|
| 1399 |
+
"samsung": "5",
|
| 1400 |
+
"rhino": "1.8",
|
| 1401 |
+
"opera_mobile": "36",
|
| 1402 |
+
"electron": "0.37"
|
| 1403 |
+
},
|
| 1404 |
+
"es6.regexp.constructor": {
|
| 1405 |
+
"chrome": "50",
|
| 1406 |
+
"opera": "37",
|
| 1407 |
+
"edge": "79",
|
| 1408 |
+
"firefox": "40",
|
| 1409 |
+
"safari": "10",
|
| 1410 |
+
"node": "6",
|
| 1411 |
+
"deno": "1",
|
| 1412 |
+
"ios": "10",
|
| 1413 |
+
"samsung": "5",
|
| 1414 |
+
"opera_mobile": "37",
|
| 1415 |
+
"electron": "1.1"
|
| 1416 |
+
},
|
| 1417 |
+
"es6.regexp.flags": {
|
| 1418 |
+
"chrome": "49",
|
| 1419 |
+
"opera": "36",
|
| 1420 |
+
"edge": "79",
|
| 1421 |
+
"firefox": "37",
|
| 1422 |
+
"safari": "9",
|
| 1423 |
+
"node": "6",
|
| 1424 |
+
"deno": "1",
|
| 1425 |
+
"ios": "9",
|
| 1426 |
+
"samsung": "5",
|
| 1427 |
+
"rhino": "1.7.15",
|
| 1428 |
+
"opera_mobile": "36",
|
| 1429 |
+
"electron": "0.37"
|
| 1430 |
+
},
|
| 1431 |
+
"es6.regexp.match": {
|
| 1432 |
+
"chrome": "50",
|
| 1433 |
+
"opera": "37",
|
| 1434 |
+
"edge": "79",
|
| 1435 |
+
"firefox": "49",
|
| 1436 |
+
"safari": "10",
|
| 1437 |
+
"node": "6",
|
| 1438 |
+
"deno": "1",
|
| 1439 |
+
"ios": "10",
|
| 1440 |
+
"samsung": "5",
|
| 1441 |
+
"rhino": "1.7.13",
|
| 1442 |
+
"opera_mobile": "37",
|
| 1443 |
+
"electron": "1.1"
|
| 1444 |
+
},
|
| 1445 |
+
"es6.regexp.replace": {
|
| 1446 |
+
"chrome": "50",
|
| 1447 |
+
"opera": "37",
|
| 1448 |
+
"edge": "79",
|
| 1449 |
+
"firefox": "49",
|
| 1450 |
+
"safari": "10",
|
| 1451 |
+
"node": "6",
|
| 1452 |
+
"deno": "1",
|
| 1453 |
+
"ios": "10",
|
| 1454 |
+
"samsung": "5",
|
| 1455 |
+
"opera_mobile": "37",
|
| 1456 |
+
"electron": "1.1"
|
| 1457 |
+
},
|
| 1458 |
+
"es6.regexp.split": {
|
| 1459 |
+
"chrome": "50",
|
| 1460 |
+
"opera": "37",
|
| 1461 |
+
"edge": "79",
|
| 1462 |
+
"firefox": "49",
|
| 1463 |
+
"safari": "10",
|
| 1464 |
+
"node": "6",
|
| 1465 |
+
"deno": "1",
|
| 1466 |
+
"ios": "10",
|
| 1467 |
+
"samsung": "5",
|
| 1468 |
+
"opera_mobile": "37",
|
| 1469 |
+
"electron": "1.1"
|
| 1470 |
+
},
|
| 1471 |
+
"es6.regexp.search": {
|
| 1472 |
+
"chrome": "50",
|
| 1473 |
+
"opera": "37",
|
| 1474 |
+
"edge": "79",
|
| 1475 |
+
"firefox": "49",
|
| 1476 |
+
"safari": "10",
|
| 1477 |
+
"node": "6",
|
| 1478 |
+
"deno": "1",
|
| 1479 |
+
"ios": "10",
|
| 1480 |
+
"samsung": "5",
|
| 1481 |
+
"rhino": "1.7.13",
|
| 1482 |
+
"opera_mobile": "37",
|
| 1483 |
+
"electron": "1.1"
|
| 1484 |
+
},
|
| 1485 |
+
"es6.regexp.to-string": {
|
| 1486 |
+
"chrome": "50",
|
| 1487 |
+
"opera": "37",
|
| 1488 |
+
"edge": "79",
|
| 1489 |
+
"firefox": "39",
|
| 1490 |
+
"safari": "10",
|
| 1491 |
+
"node": "6",
|
| 1492 |
+
"deno": "1",
|
| 1493 |
+
"ios": "10",
|
| 1494 |
+
"samsung": "5",
|
| 1495 |
+
"rhino": "1.7.15",
|
| 1496 |
+
"opera_mobile": "37",
|
| 1497 |
+
"electron": "1.1"
|
| 1498 |
+
},
|
| 1499 |
+
"es6.set": {
|
| 1500 |
+
"chrome": "51",
|
| 1501 |
+
"opera": "38",
|
| 1502 |
+
"edge": "15",
|
| 1503 |
+
"firefox": "53",
|
| 1504 |
+
"safari": "10",
|
| 1505 |
+
"node": "6.5",
|
| 1506 |
+
"deno": "1",
|
| 1507 |
+
"ios": "10",
|
| 1508 |
+
"samsung": "5",
|
| 1509 |
+
"opera_mobile": "41",
|
| 1510 |
+
"electron": "1.2"
|
| 1511 |
+
},
|
| 1512 |
+
"es6.symbol": {
|
| 1513 |
+
"chrome": "51",
|
| 1514 |
+
"opera": "38",
|
| 1515 |
+
"edge": "79",
|
| 1516 |
+
"firefox": "51",
|
| 1517 |
+
"safari": "10",
|
| 1518 |
+
"node": "6.5",
|
| 1519 |
+
"deno": "1",
|
| 1520 |
+
"ios": "10",
|
| 1521 |
+
"samsung": "5",
|
| 1522 |
+
"opera_mobile": "41",
|
| 1523 |
+
"electron": "1.2"
|
| 1524 |
+
},
|
| 1525 |
+
"es7.symbol.async-iterator": {
|
| 1526 |
+
"chrome": "63",
|
| 1527 |
+
"opera": "50",
|
| 1528 |
+
"edge": "79",
|
| 1529 |
+
"firefox": "57",
|
| 1530 |
+
"safari": "12",
|
| 1531 |
+
"node": "10",
|
| 1532 |
+
"deno": "1",
|
| 1533 |
+
"ios": "12",
|
| 1534 |
+
"samsung": "8",
|
| 1535 |
+
"opera_mobile": "46",
|
| 1536 |
+
"electron": "3.0"
|
| 1537 |
+
},
|
| 1538 |
+
"es6.string.anchor": {
|
| 1539 |
+
"chrome": "5",
|
| 1540 |
+
"opera": "15",
|
| 1541 |
+
"edge": "12",
|
| 1542 |
+
"firefox": "17",
|
| 1543 |
+
"safari": "6",
|
| 1544 |
+
"node": "0.4",
|
| 1545 |
+
"deno": "1",
|
| 1546 |
+
"android": "4",
|
| 1547 |
+
"ios": "7",
|
| 1548 |
+
"phantom": "1.9",
|
| 1549 |
+
"samsung": "1",
|
| 1550 |
+
"rhino": "1.7.14",
|
| 1551 |
+
"opera_mobile": "14",
|
| 1552 |
+
"electron": "0.20"
|
| 1553 |
+
},
|
| 1554 |
+
"es6.string.big": {
|
| 1555 |
+
"chrome": "5",
|
| 1556 |
+
"opera": "15",
|
| 1557 |
+
"edge": "12",
|
| 1558 |
+
"firefox": "17",
|
| 1559 |
+
"safari": "6",
|
| 1560 |
+
"node": "0.4",
|
| 1561 |
+
"deno": "1",
|
| 1562 |
+
"android": "4",
|
| 1563 |
+
"ios": "7",
|
| 1564 |
+
"phantom": "1.9",
|
| 1565 |
+
"samsung": "1",
|
| 1566 |
+
"rhino": "1.7.14",
|
| 1567 |
+
"opera_mobile": "14",
|
| 1568 |
+
"electron": "0.20"
|
| 1569 |
+
},
|
| 1570 |
+
"es6.string.blink": {
|
| 1571 |
+
"chrome": "5",
|
| 1572 |
+
"opera": "15",
|
| 1573 |
+
"edge": "12",
|
| 1574 |
+
"firefox": "17",
|
| 1575 |
+
"safari": "6",
|
| 1576 |
+
"node": "0.4",
|
| 1577 |
+
"deno": "1",
|
| 1578 |
+
"android": "4",
|
| 1579 |
+
"ios": "7",
|
| 1580 |
+
"phantom": "1.9",
|
| 1581 |
+
"samsung": "1",
|
| 1582 |
+
"rhino": "1.7.14",
|
| 1583 |
+
"opera_mobile": "14",
|
| 1584 |
+
"electron": "0.20"
|
| 1585 |
+
},
|
| 1586 |
+
"es6.string.bold": {
|
| 1587 |
+
"chrome": "5",
|
| 1588 |
+
"opera": "15",
|
| 1589 |
+
"edge": "12",
|
| 1590 |
+
"firefox": "17",
|
| 1591 |
+
"safari": "6",
|
| 1592 |
+
"node": "0.4",
|
| 1593 |
+
"deno": "1",
|
| 1594 |
+
"android": "4",
|
| 1595 |
+
"ios": "7",
|
| 1596 |
+
"phantom": "1.9",
|
| 1597 |
+
"samsung": "1",
|
| 1598 |
+
"rhino": "1.7.14",
|
| 1599 |
+
"opera_mobile": "14",
|
| 1600 |
+
"electron": "0.20"
|
| 1601 |
+
},
|
| 1602 |
+
"es6.string.code-point-at": {
|
| 1603 |
+
"chrome": "41",
|
| 1604 |
+
"opera": "28",
|
| 1605 |
+
"edge": "12",
|
| 1606 |
+
"firefox": "29",
|
| 1607 |
+
"safari": "9",
|
| 1608 |
+
"node": "4",
|
| 1609 |
+
"deno": "1",
|
| 1610 |
+
"ios": "9",
|
| 1611 |
+
"samsung": "3.4",
|
| 1612 |
+
"rhino": "1.7.13",
|
| 1613 |
+
"opera_mobile": "28",
|
| 1614 |
+
"electron": "0.21"
|
| 1615 |
+
},
|
| 1616 |
+
"es6.string.ends-with": {
|
| 1617 |
+
"chrome": "41",
|
| 1618 |
+
"opera": "28",
|
| 1619 |
+
"edge": "12",
|
| 1620 |
+
"firefox": "29",
|
| 1621 |
+
"safari": "9",
|
| 1622 |
+
"node": "4",
|
| 1623 |
+
"deno": "1",
|
| 1624 |
+
"ios": "9",
|
| 1625 |
+
"samsung": "3.4",
|
| 1626 |
+
"rhino": "1.7.13",
|
| 1627 |
+
"opera_mobile": "28",
|
| 1628 |
+
"electron": "0.21"
|
| 1629 |
+
},
|
| 1630 |
+
"es6.string.fixed": {
|
| 1631 |
+
"chrome": "5",
|
| 1632 |
+
"opera": "15",
|
| 1633 |
+
"edge": "12",
|
| 1634 |
+
"firefox": "17",
|
| 1635 |
+
"safari": "6",
|
| 1636 |
+
"node": "0.4",
|
| 1637 |
+
"deno": "1",
|
| 1638 |
+
"android": "4",
|
| 1639 |
+
"ios": "7",
|
| 1640 |
+
"phantom": "1.9",
|
| 1641 |
+
"samsung": "1",
|
| 1642 |
+
"rhino": "1.7.14",
|
| 1643 |
+
"opera_mobile": "14",
|
| 1644 |
+
"electron": "0.20"
|
| 1645 |
+
},
|
| 1646 |
+
"es6.string.fontcolor": {
|
| 1647 |
+
"chrome": "5",
|
| 1648 |
+
"opera": "15",
|
| 1649 |
+
"edge": "12",
|
| 1650 |
+
"firefox": "17",
|
| 1651 |
+
"safari": "6",
|
| 1652 |
+
"node": "0.4",
|
| 1653 |
+
"deno": "1",
|
| 1654 |
+
"android": "4",
|
| 1655 |
+
"ios": "7",
|
| 1656 |
+
"phantom": "1.9",
|
| 1657 |
+
"samsung": "1",
|
| 1658 |
+
"rhino": "1.7.14",
|
| 1659 |
+
"opera_mobile": "14",
|
| 1660 |
+
"electron": "0.20"
|
| 1661 |
+
},
|
| 1662 |
+
"es6.string.fontsize": {
|
| 1663 |
+
"chrome": "5",
|
| 1664 |
+
"opera": "15",
|
| 1665 |
+
"edge": "12",
|
| 1666 |
+
"firefox": "17",
|
| 1667 |
+
"safari": "6",
|
| 1668 |
+
"node": "0.4",
|
| 1669 |
+
"deno": "1",
|
| 1670 |
+
"android": "4",
|
| 1671 |
+
"ios": "7",
|
| 1672 |
+
"phantom": "1.9",
|
| 1673 |
+
"samsung": "1",
|
| 1674 |
+
"rhino": "1.7.14",
|
| 1675 |
+
"opera_mobile": "14",
|
| 1676 |
+
"electron": "0.20"
|
| 1677 |
+
},
|
| 1678 |
+
"es6.string.from-code-point": {
|
| 1679 |
+
"chrome": "41",
|
| 1680 |
+
"opera": "28",
|
| 1681 |
+
"edge": "12",
|
| 1682 |
+
"firefox": "29",
|
| 1683 |
+
"safari": "9",
|
| 1684 |
+
"node": "4",
|
| 1685 |
+
"deno": "1",
|
| 1686 |
+
"ios": "9",
|
| 1687 |
+
"samsung": "3.4",
|
| 1688 |
+
"rhino": "1.7.13",
|
| 1689 |
+
"opera_mobile": "28",
|
| 1690 |
+
"electron": "0.21"
|
| 1691 |
+
},
|
| 1692 |
+
"es6.string.includes": {
|
| 1693 |
+
"chrome": "41",
|
| 1694 |
+
"opera": "28",
|
| 1695 |
+
"edge": "12",
|
| 1696 |
+
"firefox": "40",
|
| 1697 |
+
"safari": "9",
|
| 1698 |
+
"node": "4",
|
| 1699 |
+
"deno": "1",
|
| 1700 |
+
"ios": "9",
|
| 1701 |
+
"samsung": "3.4",
|
| 1702 |
+
"rhino": "1.7.13",
|
| 1703 |
+
"opera_mobile": "28",
|
| 1704 |
+
"electron": "0.21"
|
| 1705 |
+
},
|
| 1706 |
+
"es6.string.italics": {
|
| 1707 |
+
"chrome": "5",
|
| 1708 |
+
"opera": "15",
|
| 1709 |
+
"edge": "12",
|
| 1710 |
+
"firefox": "17",
|
| 1711 |
+
"safari": "6",
|
| 1712 |
+
"node": "0.4",
|
| 1713 |
+
"deno": "1",
|
| 1714 |
+
"android": "4",
|
| 1715 |
+
"ios": "7",
|
| 1716 |
+
"phantom": "1.9",
|
| 1717 |
+
"samsung": "1",
|
| 1718 |
+
"rhino": "1.7.14",
|
| 1719 |
+
"opera_mobile": "14",
|
| 1720 |
+
"electron": "0.20"
|
| 1721 |
+
},
|
| 1722 |
+
"es6.string.iterator": {
|
| 1723 |
+
"chrome": "38",
|
| 1724 |
+
"opera": "25",
|
| 1725 |
+
"edge": "12",
|
| 1726 |
+
"firefox": "36",
|
| 1727 |
+
"safari": "9",
|
| 1728 |
+
"node": "0.12",
|
| 1729 |
+
"deno": "1",
|
| 1730 |
+
"ios": "9",
|
| 1731 |
+
"samsung": "3",
|
| 1732 |
+
"rhino": "1.7.13",
|
| 1733 |
+
"opera_mobile": "25",
|
| 1734 |
+
"electron": "0.20"
|
| 1735 |
+
},
|
| 1736 |
+
"es6.string.link": {
|
| 1737 |
+
"chrome": "5",
|
| 1738 |
+
"opera": "15",
|
| 1739 |
+
"edge": "12",
|
| 1740 |
+
"firefox": "17",
|
| 1741 |
+
"safari": "6",
|
| 1742 |
+
"node": "0.4",
|
| 1743 |
+
"deno": "1",
|
| 1744 |
+
"android": "4",
|
| 1745 |
+
"ios": "7",
|
| 1746 |
+
"phantom": "1.9",
|
| 1747 |
+
"samsung": "1",
|
| 1748 |
+
"rhino": "1.7.14",
|
| 1749 |
+
"opera_mobile": "14",
|
| 1750 |
+
"electron": "0.20"
|
| 1751 |
+
},
|
| 1752 |
+
"es7.string.pad-start": {
|
| 1753 |
+
"chrome": "57",
|
| 1754 |
+
"opera": "44",
|
| 1755 |
+
"edge": "15",
|
| 1756 |
+
"firefox": "48",
|
| 1757 |
+
"safari": "10",
|
| 1758 |
+
"node": "8",
|
| 1759 |
+
"deno": "1",
|
| 1760 |
+
"ios": "10",
|
| 1761 |
+
"samsung": "7",
|
| 1762 |
+
"rhino": "1.7.13",
|
| 1763 |
+
"opera_mobile": "43",
|
| 1764 |
+
"electron": "1.7"
|
| 1765 |
+
},
|
| 1766 |
+
"es7.string.pad-end": {
|
| 1767 |
+
"chrome": "57",
|
| 1768 |
+
"opera": "44",
|
| 1769 |
+
"edge": "15",
|
| 1770 |
+
"firefox": "48",
|
| 1771 |
+
"safari": "10",
|
| 1772 |
+
"node": "8",
|
| 1773 |
+
"deno": "1",
|
| 1774 |
+
"ios": "10",
|
| 1775 |
+
"samsung": "7",
|
| 1776 |
+
"rhino": "1.7.13",
|
| 1777 |
+
"opera_mobile": "43",
|
| 1778 |
+
"electron": "1.7"
|
| 1779 |
+
},
|
| 1780 |
+
"es6.string.raw": {
|
| 1781 |
+
"chrome": "41",
|
| 1782 |
+
"opera": "28",
|
| 1783 |
+
"edge": "12",
|
| 1784 |
+
"firefox": "34",
|
| 1785 |
+
"safari": "9",
|
| 1786 |
+
"node": "4",
|
| 1787 |
+
"deno": "1",
|
| 1788 |
+
"ios": "9",
|
| 1789 |
+
"samsung": "3.4",
|
| 1790 |
+
"rhino": "1.7.14",
|
| 1791 |
+
"opera_mobile": "28",
|
| 1792 |
+
"electron": "0.21"
|
| 1793 |
+
},
|
| 1794 |
+
"es6.string.repeat": {
|
| 1795 |
+
"chrome": "41",
|
| 1796 |
+
"opera": "28",
|
| 1797 |
+
"edge": "12",
|
| 1798 |
+
"firefox": "24",
|
| 1799 |
+
"safari": "9",
|
| 1800 |
+
"node": "4",
|
| 1801 |
+
"deno": "1",
|
| 1802 |
+
"ios": "9",
|
| 1803 |
+
"samsung": "3.4",
|
| 1804 |
+
"rhino": "1.7.13",
|
| 1805 |
+
"opera_mobile": "28",
|
| 1806 |
+
"electron": "0.21"
|
| 1807 |
+
},
|
| 1808 |
+
"es6.string.small": {
|
| 1809 |
+
"chrome": "5",
|
| 1810 |
+
"opera": "15",
|
| 1811 |
+
"edge": "12",
|
| 1812 |
+
"firefox": "17",
|
| 1813 |
+
"safari": "6",
|
| 1814 |
+
"node": "0.4",
|
| 1815 |
+
"deno": "1",
|
| 1816 |
+
"android": "4",
|
| 1817 |
+
"ios": "7",
|
| 1818 |
+
"phantom": "1.9",
|
| 1819 |
+
"samsung": "1",
|
| 1820 |
+
"rhino": "1.7.14",
|
| 1821 |
+
"opera_mobile": "14",
|
| 1822 |
+
"electron": "0.20"
|
| 1823 |
+
},
|
| 1824 |
+
"es6.string.starts-with": {
|
| 1825 |
+
"chrome": "41",
|
| 1826 |
+
"opera": "28",
|
| 1827 |
+
"edge": "12",
|
| 1828 |
+
"firefox": "29",
|
| 1829 |
+
"safari": "9",
|
| 1830 |
+
"node": "4",
|
| 1831 |
+
"deno": "1",
|
| 1832 |
+
"ios": "9",
|
| 1833 |
+
"samsung": "3.4",
|
| 1834 |
+
"rhino": "1.7.13",
|
| 1835 |
+
"opera_mobile": "28",
|
| 1836 |
+
"electron": "0.21"
|
| 1837 |
+
},
|
| 1838 |
+
"es6.string.strike": {
|
| 1839 |
+
"chrome": "5",
|
| 1840 |
+
"opera": "15",
|
| 1841 |
+
"edge": "12",
|
| 1842 |
+
"firefox": "17",
|
| 1843 |
+
"safari": "6",
|
| 1844 |
+
"node": "0.4",
|
| 1845 |
+
"deno": "1",
|
| 1846 |
+
"android": "4",
|
| 1847 |
+
"ios": "7",
|
| 1848 |
+
"phantom": "1.9",
|
| 1849 |
+
"samsung": "1",
|
| 1850 |
+
"rhino": "1.7.14",
|
| 1851 |
+
"opera_mobile": "14",
|
| 1852 |
+
"electron": "0.20"
|
| 1853 |
+
},
|
| 1854 |
+
"es6.string.sub": {
|
| 1855 |
+
"chrome": "5",
|
| 1856 |
+
"opera": "15",
|
| 1857 |
+
"edge": "12",
|
| 1858 |
+
"firefox": "17",
|
| 1859 |
+
"safari": "6",
|
| 1860 |
+
"node": "0.4",
|
| 1861 |
+
"deno": "1",
|
| 1862 |
+
"android": "4",
|
| 1863 |
+
"ios": "7",
|
| 1864 |
+
"phantom": "1.9",
|
| 1865 |
+
"samsung": "1",
|
| 1866 |
+
"rhino": "1.7.14",
|
| 1867 |
+
"opera_mobile": "14",
|
| 1868 |
+
"electron": "0.20"
|
| 1869 |
+
},
|
| 1870 |
+
"es6.string.sup": {
|
| 1871 |
+
"chrome": "5",
|
| 1872 |
+
"opera": "15",
|
| 1873 |
+
"edge": "12",
|
| 1874 |
+
"firefox": "17",
|
| 1875 |
+
"safari": "6",
|
| 1876 |
+
"node": "0.4",
|
| 1877 |
+
"deno": "1",
|
| 1878 |
+
"android": "4",
|
| 1879 |
+
"ios": "7",
|
| 1880 |
+
"phantom": "1.9",
|
| 1881 |
+
"samsung": "1",
|
| 1882 |
+
"rhino": "1.7.14",
|
| 1883 |
+
"opera_mobile": "14",
|
| 1884 |
+
"electron": "0.20"
|
| 1885 |
+
},
|
| 1886 |
+
"es6.string.trim": {
|
| 1887 |
+
"chrome": "5",
|
| 1888 |
+
"opera": "10.50",
|
| 1889 |
+
"edge": "12",
|
| 1890 |
+
"firefox": "3.5",
|
| 1891 |
+
"safari": "4",
|
| 1892 |
+
"node": "0.4",
|
| 1893 |
+
"deno": "1",
|
| 1894 |
+
"ie": "9",
|
| 1895 |
+
"android": "4",
|
| 1896 |
+
"ios": "6",
|
| 1897 |
+
"phantom": "1.9",
|
| 1898 |
+
"samsung": "1",
|
| 1899 |
+
"rhino": "1.7.13",
|
| 1900 |
+
"opera_mobile": "10.1",
|
| 1901 |
+
"electron": "0.20"
|
| 1902 |
+
},
|
| 1903 |
+
"es7.string.trim-left": {
|
| 1904 |
+
"chrome": "66",
|
| 1905 |
+
"opera": "53",
|
| 1906 |
+
"edge": "79",
|
| 1907 |
+
"firefox": "61",
|
| 1908 |
+
"safari": "12",
|
| 1909 |
+
"node": "10",
|
| 1910 |
+
"deno": "1",
|
| 1911 |
+
"ios": "12",
|
| 1912 |
+
"samsung": "9",
|
| 1913 |
+
"rhino": "1.7.13",
|
| 1914 |
+
"opera_mobile": "47",
|
| 1915 |
+
"electron": "3.0"
|
| 1916 |
+
},
|
| 1917 |
+
"es7.string.trim-right": {
|
| 1918 |
+
"chrome": "66",
|
| 1919 |
+
"opera": "53",
|
| 1920 |
+
"edge": "79",
|
| 1921 |
+
"firefox": "61",
|
| 1922 |
+
"safari": "12",
|
| 1923 |
+
"node": "10",
|
| 1924 |
+
"deno": "1",
|
| 1925 |
+
"ios": "12",
|
| 1926 |
+
"samsung": "9",
|
| 1927 |
+
"rhino": "1.7.13",
|
| 1928 |
+
"opera_mobile": "47",
|
| 1929 |
+
"electron": "3.0"
|
| 1930 |
+
},
|
| 1931 |
+
"es6.typed.array-buffer": {
|
| 1932 |
+
"chrome": "51",
|
| 1933 |
+
"opera": "38",
|
| 1934 |
+
"edge": "13",
|
| 1935 |
+
"firefox": "48",
|
| 1936 |
+
"safari": "10",
|
| 1937 |
+
"node": "6.5",
|
| 1938 |
+
"deno": "1",
|
| 1939 |
+
"ios": "10",
|
| 1940 |
+
"samsung": "5",
|
| 1941 |
+
"opera_mobile": "41",
|
| 1942 |
+
"electron": "1.2"
|
| 1943 |
+
},
|
| 1944 |
+
"es6.typed.data-view": {
|
| 1945 |
+
"chrome": "5",
|
| 1946 |
+
"opera": "12",
|
| 1947 |
+
"edge": "12",
|
| 1948 |
+
"firefox": "15",
|
| 1949 |
+
"safari": "5.1",
|
| 1950 |
+
"node": "0.4",
|
| 1951 |
+
"deno": "1",
|
| 1952 |
+
"ie": "10",
|
| 1953 |
+
"android": "4",
|
| 1954 |
+
"ios": "6",
|
| 1955 |
+
"phantom": "1.9",
|
| 1956 |
+
"samsung": "1",
|
| 1957 |
+
"rhino": "1.7.13",
|
| 1958 |
+
"opera_mobile": "12",
|
| 1959 |
+
"electron": "0.20"
|
| 1960 |
+
},
|
| 1961 |
+
"es6.typed.int8-array": {
|
| 1962 |
+
"chrome": "51",
|
| 1963 |
+
"opera": "38",
|
| 1964 |
+
"edge": "13",
|
| 1965 |
+
"firefox": "48",
|
| 1966 |
+
"safari": "10",
|
| 1967 |
+
"node": "6.5",
|
| 1968 |
+
"deno": "1",
|
| 1969 |
+
"ios": "10",
|
| 1970 |
+
"samsung": "5",
|
| 1971 |
+
"opera_mobile": "41",
|
| 1972 |
+
"electron": "1.2"
|
| 1973 |
+
},
|
| 1974 |
+
"es6.typed.uint8-array": {
|
| 1975 |
+
"chrome": "51",
|
| 1976 |
+
"opera": "38",
|
| 1977 |
+
"edge": "13",
|
| 1978 |
+
"firefox": "48",
|
| 1979 |
+
"safari": "10",
|
| 1980 |
+
"node": "6.5",
|
| 1981 |
+
"deno": "1",
|
| 1982 |
+
"ios": "10",
|
| 1983 |
+
"samsung": "5",
|
| 1984 |
+
"opera_mobile": "41",
|
| 1985 |
+
"electron": "1.2"
|
| 1986 |
+
},
|
| 1987 |
+
"es6.typed.uint8-clamped-array": {
|
| 1988 |
+
"chrome": "51",
|
| 1989 |
+
"opera": "38",
|
| 1990 |
+
"edge": "13",
|
| 1991 |
+
"firefox": "48",
|
| 1992 |
+
"safari": "10",
|
| 1993 |
+
"node": "6.5",
|
| 1994 |
+
"deno": "1",
|
| 1995 |
+
"ios": "10",
|
| 1996 |
+
"samsung": "5",
|
| 1997 |
+
"opera_mobile": "41",
|
| 1998 |
+
"electron": "1.2"
|
| 1999 |
+
},
|
| 2000 |
+
"es6.typed.int16-array": {
|
| 2001 |
+
"chrome": "51",
|
| 2002 |
+
"opera": "38",
|
| 2003 |
+
"edge": "13",
|
| 2004 |
+
"firefox": "48",
|
| 2005 |
+
"safari": "10",
|
| 2006 |
+
"node": "6.5",
|
| 2007 |
+
"deno": "1",
|
| 2008 |
+
"ios": "10",
|
| 2009 |
+
"samsung": "5",
|
| 2010 |
+
"opera_mobile": "41",
|
| 2011 |
+
"electron": "1.2"
|
| 2012 |
+
},
|
| 2013 |
+
"es6.typed.uint16-array": {
|
| 2014 |
+
"chrome": "51",
|
| 2015 |
+
"opera": "38",
|
| 2016 |
+
"edge": "13",
|
| 2017 |
+
"firefox": "48",
|
| 2018 |
+
"safari": "10",
|
| 2019 |
+
"node": "6.5",
|
| 2020 |
+
"deno": "1",
|
| 2021 |
+
"ios": "10",
|
| 2022 |
+
"samsung": "5",
|
| 2023 |
+
"opera_mobile": "41",
|
| 2024 |
+
"electron": "1.2"
|
| 2025 |
+
},
|
| 2026 |
+
"es6.typed.int32-array": {
|
| 2027 |
+
"chrome": "51",
|
| 2028 |
+
"opera": "38",
|
| 2029 |
+
"edge": "13",
|
| 2030 |
+
"firefox": "48",
|
| 2031 |
+
"safari": "10",
|
| 2032 |
+
"node": "6.5",
|
| 2033 |
+
"deno": "1",
|
| 2034 |
+
"ios": "10",
|
| 2035 |
+
"samsung": "5",
|
| 2036 |
+
"opera_mobile": "41",
|
| 2037 |
+
"electron": "1.2"
|
| 2038 |
+
},
|
| 2039 |
+
"es6.typed.uint32-array": {
|
| 2040 |
+
"chrome": "51",
|
| 2041 |
+
"opera": "38",
|
| 2042 |
+
"edge": "13",
|
| 2043 |
+
"firefox": "48",
|
| 2044 |
+
"safari": "10",
|
| 2045 |
+
"node": "6.5",
|
| 2046 |
+
"deno": "1",
|
| 2047 |
+
"ios": "10",
|
| 2048 |
+
"samsung": "5",
|
| 2049 |
+
"opera_mobile": "41",
|
| 2050 |
+
"electron": "1.2"
|
| 2051 |
+
},
|
| 2052 |
+
"es6.typed.float32-array": {
|
| 2053 |
+
"chrome": "51",
|
| 2054 |
+
"opera": "38",
|
| 2055 |
+
"edge": "13",
|
| 2056 |
+
"firefox": "48",
|
| 2057 |
+
"safari": "10",
|
| 2058 |
+
"node": "6.5",
|
| 2059 |
+
"deno": "1",
|
| 2060 |
+
"ios": "10",
|
| 2061 |
+
"samsung": "5",
|
| 2062 |
+
"opera_mobile": "41",
|
| 2063 |
+
"electron": "1.2"
|
| 2064 |
+
},
|
| 2065 |
+
"es6.typed.float64-array": {
|
| 2066 |
+
"chrome": "51",
|
| 2067 |
+
"opera": "38",
|
| 2068 |
+
"edge": "13",
|
| 2069 |
+
"firefox": "48",
|
| 2070 |
+
"safari": "10",
|
| 2071 |
+
"node": "6.5",
|
| 2072 |
+
"deno": "1",
|
| 2073 |
+
"ios": "10",
|
| 2074 |
+
"samsung": "5",
|
| 2075 |
+
"opera_mobile": "41",
|
| 2076 |
+
"electron": "1.2"
|
| 2077 |
+
},
|
| 2078 |
+
"es6.weak-map": {
|
| 2079 |
+
"chrome": "51",
|
| 2080 |
+
"opera": "38",
|
| 2081 |
+
"edge": "15",
|
| 2082 |
+
"firefox": "53",
|
| 2083 |
+
"safari": "9",
|
| 2084 |
+
"node": "6.5",
|
| 2085 |
+
"deno": "1",
|
| 2086 |
+
"ios": "9",
|
| 2087 |
+
"samsung": "5",
|
| 2088 |
+
"rhino": "1.7.15",
|
| 2089 |
+
"opera_mobile": "41",
|
| 2090 |
+
"electron": "1.2"
|
| 2091 |
+
},
|
| 2092 |
+
"es6.weak-set": {
|
| 2093 |
+
"chrome": "51",
|
| 2094 |
+
"opera": "38",
|
| 2095 |
+
"edge": "15",
|
| 2096 |
+
"firefox": "53",
|
| 2097 |
+
"safari": "9",
|
| 2098 |
+
"node": "6.5",
|
| 2099 |
+
"deno": "1",
|
| 2100 |
+
"ios": "9",
|
| 2101 |
+
"samsung": "5",
|
| 2102 |
+
"rhino": "1.7.15",
|
| 2103 |
+
"opera_mobile": "41",
|
| 2104 |
+
"electron": "1.2"
|
| 2105 |
+
}
|
| 2106 |
+
}
|
dashboard/frontend/node_modules/@babel/compat-data/data/corejs3-shipped-proposals.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
"esnext.promise.all-settled",
|
| 3 |
+
"esnext.string.match-all",
|
| 4 |
+
"esnext.global-this"
|
| 5 |
+
]
|
dashboard/frontend/node_modules/@babel/compat-data/data/native-modules.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"es6.module": {
|
| 3 |
+
"chrome": "61",
|
| 4 |
+
"and_chr": "61",
|
| 5 |
+
"edge": "16",
|
| 6 |
+
"firefox": "60",
|
| 7 |
+
"and_ff": "60",
|
| 8 |
+
"node": "13.2.0",
|
| 9 |
+
"opera": "48",
|
| 10 |
+
"op_mob": "45",
|
| 11 |
+
"safari": "10.1",
|
| 12 |
+
"ios": "10.3",
|
| 13 |
+
"samsung": "8.2",
|
| 14 |
+
"android": "61",
|
| 15 |
+
"electron": "2.0",
|
| 16 |
+
"ios_saf": "10.3"
|
| 17 |
+
}
|
| 18 |
+
}
|
dashboard/frontend/node_modules/@babel/compat-data/data/overlapping-plugins.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"transform-async-to-generator": [
|
| 3 |
+
"bugfix/transform-async-arrows-in-class"
|
| 4 |
+
],
|
| 5 |
+
"transform-parameters": [
|
| 6 |
+
"bugfix/transform-edge-default-parameters",
|
| 7 |
+
"bugfix/transform-safari-id-destructuring-collision-in-function-expression"
|
| 8 |
+
],
|
| 9 |
+
"transform-function-name": [
|
| 10 |
+
"bugfix/transform-edge-function-name"
|
| 11 |
+
],
|
| 12 |
+
"transform-block-scoping": [
|
| 13 |
+
"bugfix/transform-safari-block-shadowing",
|
| 14 |
+
"bugfix/transform-safari-for-shadowing"
|
| 15 |
+
],
|
| 16 |
+
"transform-template-literals": [
|
| 17 |
+
"bugfix/transform-tagged-template-caching"
|
| 18 |
+
],
|
| 19 |
+
"transform-optional-chaining": [
|
| 20 |
+
"bugfix/transform-v8-spread-parameters-in-optional-chaining"
|
| 21 |
+
],
|
| 22 |
+
"proposal-optional-chaining": [
|
| 23 |
+
"bugfix/transform-v8-spread-parameters-in-optional-chaining"
|
| 24 |
+
],
|
| 25 |
+
"transform-class-properties": [
|
| 26 |
+
"bugfix/transform-v8-static-class-fields-redefine-readonly",
|
| 27 |
+
"bugfix/transform-firefox-class-in-computed-class-key",
|
| 28 |
+
"bugfix/transform-safari-class-field-initializer-scope"
|
| 29 |
+
],
|
| 30 |
+
"proposal-class-properties": [
|
| 31 |
+
"bugfix/transform-v8-static-class-fields-redefine-readonly",
|
| 32 |
+
"bugfix/transform-firefox-class-in-computed-class-key",
|
| 33 |
+
"bugfix/transform-safari-class-field-initializer-scope"
|
| 34 |
+
]
|
| 35 |
+
}
|
dashboard/frontend/node_modules/@babel/compat-data/data/plugin-bugfixes.json
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bugfix/transform-async-arrows-in-class": {
|
| 3 |
+
"chrome": "55",
|
| 4 |
+
"opera": "42",
|
| 5 |
+
"edge": "15",
|
| 6 |
+
"firefox": "52",
|
| 7 |
+
"safari": "11",
|
| 8 |
+
"node": "7.6",
|
| 9 |
+
"deno": "1",
|
| 10 |
+
"ios": "11",
|
| 11 |
+
"samsung": "6",
|
| 12 |
+
"opera_mobile": "42",
|
| 13 |
+
"electron": "1.6"
|
| 14 |
+
},
|
| 15 |
+
"bugfix/transform-edge-default-parameters": {
|
| 16 |
+
"chrome": "49",
|
| 17 |
+
"opera": "36",
|
| 18 |
+
"edge": "18",
|
| 19 |
+
"firefox": "52",
|
| 20 |
+
"safari": "10",
|
| 21 |
+
"node": "6",
|
| 22 |
+
"deno": "1",
|
| 23 |
+
"ios": "10",
|
| 24 |
+
"samsung": "5",
|
| 25 |
+
"opera_mobile": "36",
|
| 26 |
+
"electron": "0.37"
|
| 27 |
+
},
|
| 28 |
+
"bugfix/transform-edge-function-name": {
|
| 29 |
+
"chrome": "51",
|
| 30 |
+
"opera": "38",
|
| 31 |
+
"edge": "79",
|
| 32 |
+
"firefox": "53",
|
| 33 |
+
"safari": "10",
|
| 34 |
+
"node": "6.5",
|
| 35 |
+
"deno": "1",
|
| 36 |
+
"ios": "10",
|
| 37 |
+
"samsung": "5",
|
| 38 |
+
"opera_mobile": "41",
|
| 39 |
+
"electron": "1.2"
|
| 40 |
+
},
|
| 41 |
+
"bugfix/transform-safari-block-shadowing": {
|
| 42 |
+
"chrome": "49",
|
| 43 |
+
"opera": "36",
|
| 44 |
+
"edge": "12",
|
| 45 |
+
"firefox": "44",
|
| 46 |
+
"safari": "11",
|
| 47 |
+
"node": "6",
|
| 48 |
+
"deno": "1",
|
| 49 |
+
"ie": "11",
|
| 50 |
+
"ios": "11",
|
| 51 |
+
"samsung": "5",
|
| 52 |
+
"opera_mobile": "36",
|
| 53 |
+
"electron": "0.37"
|
| 54 |
+
},
|
| 55 |
+
"bugfix/transform-safari-for-shadowing": {
|
| 56 |
+
"chrome": "49",
|
| 57 |
+
"opera": "36",
|
| 58 |
+
"edge": "12",
|
| 59 |
+
"firefox": "4",
|
| 60 |
+
"safari": "11",
|
| 61 |
+
"node": "6",
|
| 62 |
+
"deno": "1",
|
| 63 |
+
"ie": "11",
|
| 64 |
+
"ios": "11",
|
| 65 |
+
"samsung": "5",
|
| 66 |
+
"rhino": "1.7.13",
|
| 67 |
+
"opera_mobile": "36",
|
| 68 |
+
"electron": "0.37"
|
| 69 |
+
},
|
| 70 |
+
"bugfix/transform-safari-id-destructuring-collision-in-function-expression": {
|
| 71 |
+
"chrome": "49",
|
| 72 |
+
"opera": "36",
|
| 73 |
+
"edge": "14",
|
| 74 |
+
"firefox": "2",
|
| 75 |
+
"safari": "16.3",
|
| 76 |
+
"node": "6",
|
| 77 |
+
"deno": "1",
|
| 78 |
+
"ios": "16.3",
|
| 79 |
+
"samsung": "5",
|
| 80 |
+
"opera_mobile": "36",
|
| 81 |
+
"electron": "0.37"
|
| 82 |
+
},
|
| 83 |
+
"bugfix/transform-tagged-template-caching": {
|
| 84 |
+
"chrome": "41",
|
| 85 |
+
"opera": "28",
|
| 86 |
+
"edge": "12",
|
| 87 |
+
"firefox": "34",
|
| 88 |
+
"safari": "13",
|
| 89 |
+
"node": "4",
|
| 90 |
+
"deno": "1",
|
| 91 |
+
"ios": "13",
|
| 92 |
+
"samsung": "3.4",
|
| 93 |
+
"rhino": "1.7.14",
|
| 94 |
+
"opera_mobile": "28",
|
| 95 |
+
"electron": "0.21"
|
| 96 |
+
},
|
| 97 |
+
"bugfix/transform-v8-spread-parameters-in-optional-chaining": {
|
| 98 |
+
"chrome": "91",
|
| 99 |
+
"opera": "77",
|
| 100 |
+
"edge": "91",
|
| 101 |
+
"firefox": "74",
|
| 102 |
+
"safari": "13.1",
|
| 103 |
+
"node": "16.9",
|
| 104 |
+
"deno": "1.9",
|
| 105 |
+
"ios": "13.4",
|
| 106 |
+
"samsung": "16",
|
| 107 |
+
"opera_mobile": "64",
|
| 108 |
+
"electron": "13.0"
|
| 109 |
+
},
|
| 110 |
+
"transform-optional-chaining": {
|
| 111 |
+
"chrome": "80",
|
| 112 |
+
"opera": "67",
|
| 113 |
+
"edge": "80",
|
| 114 |
+
"firefox": "74",
|
| 115 |
+
"safari": "13.1",
|
| 116 |
+
"node": "14",
|
| 117 |
+
"deno": "1",
|
| 118 |
+
"ios": "13.4",
|
| 119 |
+
"samsung": "13",
|
| 120 |
+
"rhino": "1.8",
|
| 121 |
+
"opera_mobile": "57",
|
| 122 |
+
"electron": "8.0"
|
| 123 |
+
},
|
| 124 |
+
"proposal-optional-chaining": {
|
| 125 |
+
"chrome": "80",
|
| 126 |
+
"opera": "67",
|
| 127 |
+
"edge": "80",
|
| 128 |
+
"firefox": "74",
|
| 129 |
+
"safari": "13.1",
|
| 130 |
+
"node": "14",
|
| 131 |
+
"deno": "1",
|
| 132 |
+
"ios": "13.4",
|
| 133 |
+
"samsung": "13",
|
| 134 |
+
"rhino": "1.8",
|
| 135 |
+
"opera_mobile": "57",
|
| 136 |
+
"electron": "8.0"
|
| 137 |
+
},
|
| 138 |
+
"transform-parameters": {
|
| 139 |
+
"chrome": "49",
|
| 140 |
+
"opera": "36",
|
| 141 |
+
"edge": "15",
|
| 142 |
+
"firefox": "52",
|
| 143 |
+
"safari": "10",
|
| 144 |
+
"node": "6",
|
| 145 |
+
"deno": "1",
|
| 146 |
+
"ios": "10",
|
| 147 |
+
"samsung": "5",
|
| 148 |
+
"opera_mobile": "36",
|
| 149 |
+
"electron": "0.37"
|
| 150 |
+
},
|
| 151 |
+
"transform-async-to-generator": {
|
| 152 |
+
"chrome": "55",
|
| 153 |
+
"opera": "42",
|
| 154 |
+
"edge": "15",
|
| 155 |
+
"firefox": "52",
|
| 156 |
+
"safari": "10.1",
|
| 157 |
+
"node": "7.6",
|
| 158 |
+
"deno": "1",
|
| 159 |
+
"ios": "10.3",
|
| 160 |
+
"samsung": "6",
|
| 161 |
+
"opera_mobile": "42",
|
| 162 |
+
"electron": "1.6"
|
| 163 |
+
},
|
| 164 |
+
"transform-template-literals": {
|
| 165 |
+
"chrome": "41",
|
| 166 |
+
"opera": "28",
|
| 167 |
+
"edge": "13",
|
| 168 |
+
"firefox": "34",
|
| 169 |
+
"safari": "9",
|
| 170 |
+
"node": "4",
|
| 171 |
+
"deno": "1",
|
| 172 |
+
"ios": "9",
|
| 173 |
+
"samsung": "3.4",
|
| 174 |
+
"opera_mobile": "28",
|
| 175 |
+
"electron": "0.21"
|
| 176 |
+
},
|
| 177 |
+
"transform-function-name": {
|
| 178 |
+
"chrome": "51",
|
| 179 |
+
"opera": "38",
|
| 180 |
+
"edge": "14",
|
| 181 |
+
"firefox": "53",
|
| 182 |
+
"safari": "10",
|
| 183 |
+
"node": "6.5",
|
| 184 |
+
"deno": "1",
|
| 185 |
+
"ios": "10",
|
| 186 |
+
"samsung": "5",
|
| 187 |
+
"opera_mobile": "41",
|
| 188 |
+
"electron": "1.2"
|
| 189 |
+
},
|
| 190 |
+
"transform-block-scoping": {
|
| 191 |
+
"chrome": "50",
|
| 192 |
+
"opera": "37",
|
| 193 |
+
"edge": "14",
|
| 194 |
+
"firefox": "53",
|
| 195 |
+
"safari": "10",
|
| 196 |
+
"node": "6",
|
| 197 |
+
"deno": "1",
|
| 198 |
+
"ios": "10",
|
| 199 |
+
"samsung": "5",
|
| 200 |
+
"opera_mobile": "37",
|
| 201 |
+
"electron": "1.1"
|
| 202 |
+
}
|
| 203 |
+
}
|
dashboard/frontend/node_modules/@babel/compat-data/data/plugins.json
ADDED
|
@@ -0,0 +1,838 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"transform-explicit-resource-management": {
|
| 3 |
+
"chrome": "134",
|
| 4 |
+
"edge": "134",
|
| 5 |
+
"firefox": "141",
|
| 6 |
+
"node": "24",
|
| 7 |
+
"electron": "35.0"
|
| 8 |
+
},
|
| 9 |
+
"transform-duplicate-named-capturing-groups-regex": {
|
| 10 |
+
"chrome": "126",
|
| 11 |
+
"opera": "112",
|
| 12 |
+
"edge": "126",
|
| 13 |
+
"firefox": "129",
|
| 14 |
+
"safari": "17.4",
|
| 15 |
+
"node": "23",
|
| 16 |
+
"ios": "17.4",
|
| 17 |
+
"electron": "31.0"
|
| 18 |
+
},
|
| 19 |
+
"transform-regexp-modifiers": {
|
| 20 |
+
"chrome": "125",
|
| 21 |
+
"opera": "111",
|
| 22 |
+
"edge": "125",
|
| 23 |
+
"firefox": "132",
|
| 24 |
+
"node": "23",
|
| 25 |
+
"samsung": "27",
|
| 26 |
+
"electron": "31.0"
|
| 27 |
+
},
|
| 28 |
+
"transform-unicode-sets-regex": {
|
| 29 |
+
"chrome": "112",
|
| 30 |
+
"opera": "98",
|
| 31 |
+
"edge": "112",
|
| 32 |
+
"firefox": "116",
|
| 33 |
+
"safari": "17",
|
| 34 |
+
"node": "20",
|
| 35 |
+
"deno": "1.32",
|
| 36 |
+
"ios": "17",
|
| 37 |
+
"samsung": "23",
|
| 38 |
+
"opera_mobile": "75",
|
| 39 |
+
"electron": "24.0"
|
| 40 |
+
},
|
| 41 |
+
"bugfix/transform-v8-static-class-fields-redefine-readonly": {
|
| 42 |
+
"chrome": "98",
|
| 43 |
+
"opera": "84",
|
| 44 |
+
"edge": "98",
|
| 45 |
+
"firefox": "75",
|
| 46 |
+
"safari": "15",
|
| 47 |
+
"node": "12",
|
| 48 |
+
"deno": "1.18",
|
| 49 |
+
"ios": "15",
|
| 50 |
+
"samsung": "11",
|
| 51 |
+
"opera_mobile": "52",
|
| 52 |
+
"electron": "17.0"
|
| 53 |
+
},
|
| 54 |
+
"bugfix/transform-firefox-class-in-computed-class-key": {
|
| 55 |
+
"chrome": "74",
|
| 56 |
+
"opera": "62",
|
| 57 |
+
"edge": "79",
|
| 58 |
+
"firefox": "126",
|
| 59 |
+
"safari": "16",
|
| 60 |
+
"node": "12",
|
| 61 |
+
"deno": "1",
|
| 62 |
+
"ios": "16",
|
| 63 |
+
"samsung": "11",
|
| 64 |
+
"opera_mobile": "53",
|
| 65 |
+
"electron": "6.0"
|
| 66 |
+
},
|
| 67 |
+
"bugfix/transform-safari-class-field-initializer-scope": {
|
| 68 |
+
"chrome": "74",
|
| 69 |
+
"opera": "62",
|
| 70 |
+
"edge": "79",
|
| 71 |
+
"firefox": "69",
|
| 72 |
+
"safari": "16",
|
| 73 |
+
"node": "12",
|
| 74 |
+
"deno": "1",
|
| 75 |
+
"ios": "16",
|
| 76 |
+
"samsung": "11",
|
| 77 |
+
"opera_mobile": "53",
|
| 78 |
+
"electron": "6.0"
|
| 79 |
+
},
|
| 80 |
+
"transform-class-static-block": {
|
| 81 |
+
"chrome": "94",
|
| 82 |
+
"opera": "80",
|
| 83 |
+
"edge": "94",
|
| 84 |
+
"firefox": "93",
|
| 85 |
+
"safari": "16.4",
|
| 86 |
+
"node": "16.11",
|
| 87 |
+
"deno": "1.14",
|
| 88 |
+
"ios": "16.4",
|
| 89 |
+
"samsung": "17",
|
| 90 |
+
"opera_mobile": "66",
|
| 91 |
+
"electron": "15.0"
|
| 92 |
+
},
|
| 93 |
+
"proposal-class-static-block": {
|
| 94 |
+
"chrome": "94",
|
| 95 |
+
"opera": "80",
|
| 96 |
+
"edge": "94",
|
| 97 |
+
"firefox": "93",
|
| 98 |
+
"safari": "16.4",
|
| 99 |
+
"node": "16.11",
|
| 100 |
+
"deno": "1.14",
|
| 101 |
+
"ios": "16.4",
|
| 102 |
+
"samsung": "17",
|
| 103 |
+
"opera_mobile": "66",
|
| 104 |
+
"electron": "15.0"
|
| 105 |
+
},
|
| 106 |
+
"transform-private-property-in-object": {
|
| 107 |
+
"chrome": "91",
|
| 108 |
+
"opera": "77",
|
| 109 |
+
"edge": "91",
|
| 110 |
+
"firefox": "90",
|
| 111 |
+
"safari": "15",
|
| 112 |
+
"node": "16.9",
|
| 113 |
+
"deno": "1.9",
|
| 114 |
+
"ios": "15",
|
| 115 |
+
"samsung": "16",
|
| 116 |
+
"opera_mobile": "64",
|
| 117 |
+
"electron": "13.0"
|
| 118 |
+
},
|
| 119 |
+
"proposal-private-property-in-object": {
|
| 120 |
+
"chrome": "91",
|
| 121 |
+
"opera": "77",
|
| 122 |
+
"edge": "91",
|
| 123 |
+
"firefox": "90",
|
| 124 |
+
"safari": "15",
|
| 125 |
+
"node": "16.9",
|
| 126 |
+
"deno": "1.9",
|
| 127 |
+
"ios": "15",
|
| 128 |
+
"samsung": "16",
|
| 129 |
+
"opera_mobile": "64",
|
| 130 |
+
"electron": "13.0"
|
| 131 |
+
},
|
| 132 |
+
"transform-class-properties": {
|
| 133 |
+
"chrome": "74",
|
| 134 |
+
"opera": "62",
|
| 135 |
+
"edge": "79",
|
| 136 |
+
"firefox": "90",
|
| 137 |
+
"safari": "14.1",
|
| 138 |
+
"node": "12",
|
| 139 |
+
"deno": "1",
|
| 140 |
+
"ios": "14.5",
|
| 141 |
+
"samsung": "11",
|
| 142 |
+
"opera_mobile": "53",
|
| 143 |
+
"electron": "6.0"
|
| 144 |
+
},
|
| 145 |
+
"proposal-class-properties": {
|
| 146 |
+
"chrome": "74",
|
| 147 |
+
"opera": "62",
|
| 148 |
+
"edge": "79",
|
| 149 |
+
"firefox": "90",
|
| 150 |
+
"safari": "14.1",
|
| 151 |
+
"node": "12",
|
| 152 |
+
"deno": "1",
|
| 153 |
+
"ios": "14.5",
|
| 154 |
+
"samsung": "11",
|
| 155 |
+
"opera_mobile": "53",
|
| 156 |
+
"electron": "6.0"
|
| 157 |
+
},
|
| 158 |
+
"transform-private-methods": {
|
| 159 |
+
"chrome": "84",
|
| 160 |
+
"opera": "70",
|
| 161 |
+
"edge": "84",
|
| 162 |
+
"firefox": "90",
|
| 163 |
+
"safari": "15",
|
| 164 |
+
"node": "14.6",
|
| 165 |
+
"deno": "1",
|
| 166 |
+
"ios": "15",
|
| 167 |
+
"samsung": "14",
|
| 168 |
+
"opera_mobile": "60",
|
| 169 |
+
"electron": "10.0"
|
| 170 |
+
},
|
| 171 |
+
"proposal-private-methods": {
|
| 172 |
+
"chrome": "84",
|
| 173 |
+
"opera": "70",
|
| 174 |
+
"edge": "84",
|
| 175 |
+
"firefox": "90",
|
| 176 |
+
"safari": "15",
|
| 177 |
+
"node": "14.6",
|
| 178 |
+
"deno": "1",
|
| 179 |
+
"ios": "15",
|
| 180 |
+
"samsung": "14",
|
| 181 |
+
"opera_mobile": "60",
|
| 182 |
+
"electron": "10.0"
|
| 183 |
+
},
|
| 184 |
+
"transform-numeric-separator": {
|
| 185 |
+
"chrome": "75",
|
| 186 |
+
"opera": "62",
|
| 187 |
+
"edge": "79",
|
| 188 |
+
"firefox": "70",
|
| 189 |
+
"safari": "13",
|
| 190 |
+
"node": "12.5",
|
| 191 |
+
"deno": "1",
|
| 192 |
+
"ios": "13",
|
| 193 |
+
"samsung": "11",
|
| 194 |
+
"rhino": "1.7.14",
|
| 195 |
+
"opera_mobile": "54",
|
| 196 |
+
"electron": "6.0"
|
| 197 |
+
},
|
| 198 |
+
"proposal-numeric-separator": {
|
| 199 |
+
"chrome": "75",
|
| 200 |
+
"opera": "62",
|
| 201 |
+
"edge": "79",
|
| 202 |
+
"firefox": "70",
|
| 203 |
+
"safari": "13",
|
| 204 |
+
"node": "12.5",
|
| 205 |
+
"deno": "1",
|
| 206 |
+
"ios": "13",
|
| 207 |
+
"samsung": "11",
|
| 208 |
+
"rhino": "1.7.14",
|
| 209 |
+
"opera_mobile": "54",
|
| 210 |
+
"electron": "6.0"
|
| 211 |
+
},
|
| 212 |
+
"transform-logical-assignment-operators": {
|
| 213 |
+
"chrome": "85",
|
| 214 |
+
"opera": "71",
|
| 215 |
+
"edge": "85",
|
| 216 |
+
"firefox": "79",
|
| 217 |
+
"safari": "14",
|
| 218 |
+
"node": "15",
|
| 219 |
+
"deno": "1.2",
|
| 220 |
+
"ios": "14",
|
| 221 |
+
"samsung": "14",
|
| 222 |
+
"opera_mobile": "60",
|
| 223 |
+
"electron": "10.0"
|
| 224 |
+
},
|
| 225 |
+
"proposal-logical-assignment-operators": {
|
| 226 |
+
"chrome": "85",
|
| 227 |
+
"opera": "71",
|
| 228 |
+
"edge": "85",
|
| 229 |
+
"firefox": "79",
|
| 230 |
+
"safari": "14",
|
| 231 |
+
"node": "15",
|
| 232 |
+
"deno": "1.2",
|
| 233 |
+
"ios": "14",
|
| 234 |
+
"samsung": "14",
|
| 235 |
+
"opera_mobile": "60",
|
| 236 |
+
"electron": "10.0"
|
| 237 |
+
},
|
| 238 |
+
"transform-nullish-coalescing-operator": {
|
| 239 |
+
"chrome": "80",
|
| 240 |
+
"opera": "67",
|
| 241 |
+
"edge": "80",
|
| 242 |
+
"firefox": "72",
|
| 243 |
+
"safari": "13.1",
|
| 244 |
+
"node": "14",
|
| 245 |
+
"deno": "1",
|
| 246 |
+
"ios": "13.4",
|
| 247 |
+
"samsung": "13",
|
| 248 |
+
"rhino": "1.8",
|
| 249 |
+
"opera_mobile": "57",
|
| 250 |
+
"electron": "8.0"
|
| 251 |
+
},
|
| 252 |
+
"proposal-nullish-coalescing-operator": {
|
| 253 |
+
"chrome": "80",
|
| 254 |
+
"opera": "67",
|
| 255 |
+
"edge": "80",
|
| 256 |
+
"firefox": "72",
|
| 257 |
+
"safari": "13.1",
|
| 258 |
+
"node": "14",
|
| 259 |
+
"deno": "1",
|
| 260 |
+
"ios": "13.4",
|
| 261 |
+
"samsung": "13",
|
| 262 |
+
"rhino": "1.8",
|
| 263 |
+
"opera_mobile": "57",
|
| 264 |
+
"electron": "8.0"
|
| 265 |
+
},
|
| 266 |
+
"transform-optional-chaining": {
|
| 267 |
+
"chrome": "91",
|
| 268 |
+
"opera": "77",
|
| 269 |
+
"edge": "91",
|
| 270 |
+
"firefox": "74",
|
| 271 |
+
"safari": "13.1",
|
| 272 |
+
"node": "16.9",
|
| 273 |
+
"deno": "1.9",
|
| 274 |
+
"ios": "13.4",
|
| 275 |
+
"samsung": "16",
|
| 276 |
+
"opera_mobile": "64",
|
| 277 |
+
"electron": "13.0"
|
| 278 |
+
},
|
| 279 |
+
"proposal-optional-chaining": {
|
| 280 |
+
"chrome": "91",
|
| 281 |
+
"opera": "77",
|
| 282 |
+
"edge": "91",
|
| 283 |
+
"firefox": "74",
|
| 284 |
+
"safari": "13.1",
|
| 285 |
+
"node": "16.9",
|
| 286 |
+
"deno": "1.9",
|
| 287 |
+
"ios": "13.4",
|
| 288 |
+
"samsung": "16",
|
| 289 |
+
"opera_mobile": "64",
|
| 290 |
+
"electron": "13.0"
|
| 291 |
+
},
|
| 292 |
+
"transform-json-strings": {
|
| 293 |
+
"chrome": "66",
|
| 294 |
+
"opera": "53",
|
| 295 |
+
"edge": "79",
|
| 296 |
+
"firefox": "62",
|
| 297 |
+
"safari": "12",
|
| 298 |
+
"node": "10",
|
| 299 |
+
"deno": "1",
|
| 300 |
+
"ios": "12",
|
| 301 |
+
"samsung": "9",
|
| 302 |
+
"rhino": "1.7.14",
|
| 303 |
+
"opera_mobile": "47",
|
| 304 |
+
"electron": "3.0"
|
| 305 |
+
},
|
| 306 |
+
"proposal-json-strings": {
|
| 307 |
+
"chrome": "66",
|
| 308 |
+
"opera": "53",
|
| 309 |
+
"edge": "79",
|
| 310 |
+
"firefox": "62",
|
| 311 |
+
"safari": "12",
|
| 312 |
+
"node": "10",
|
| 313 |
+
"deno": "1",
|
| 314 |
+
"ios": "12",
|
| 315 |
+
"samsung": "9",
|
| 316 |
+
"rhino": "1.7.14",
|
| 317 |
+
"opera_mobile": "47",
|
| 318 |
+
"electron": "3.0"
|
| 319 |
+
},
|
| 320 |
+
"transform-optional-catch-binding": {
|
| 321 |
+
"chrome": "66",
|
| 322 |
+
"opera": "53",
|
| 323 |
+
"edge": "79",
|
| 324 |
+
"firefox": "58",
|
| 325 |
+
"safari": "11.1",
|
| 326 |
+
"node": "10",
|
| 327 |
+
"deno": "1",
|
| 328 |
+
"ios": "11.3",
|
| 329 |
+
"samsung": "9",
|
| 330 |
+
"opera_mobile": "47",
|
| 331 |
+
"electron": "3.0"
|
| 332 |
+
},
|
| 333 |
+
"proposal-optional-catch-binding": {
|
| 334 |
+
"chrome": "66",
|
| 335 |
+
"opera": "53",
|
| 336 |
+
"edge": "79",
|
| 337 |
+
"firefox": "58",
|
| 338 |
+
"safari": "11.1",
|
| 339 |
+
"node": "10",
|
| 340 |
+
"deno": "1",
|
| 341 |
+
"ios": "11.3",
|
| 342 |
+
"samsung": "9",
|
| 343 |
+
"opera_mobile": "47",
|
| 344 |
+
"electron": "3.0"
|
| 345 |
+
},
|
| 346 |
+
"transform-parameters": {
|
| 347 |
+
"chrome": "49",
|
| 348 |
+
"opera": "36",
|
| 349 |
+
"edge": "18",
|
| 350 |
+
"firefox": "52",
|
| 351 |
+
"safari": "16.3",
|
| 352 |
+
"node": "6",
|
| 353 |
+
"deno": "1",
|
| 354 |
+
"ios": "16.3",
|
| 355 |
+
"samsung": "5",
|
| 356 |
+
"opera_mobile": "36",
|
| 357 |
+
"electron": "0.37"
|
| 358 |
+
},
|
| 359 |
+
"transform-async-generator-functions": {
|
| 360 |
+
"chrome": "63",
|
| 361 |
+
"opera": "50",
|
| 362 |
+
"edge": "79",
|
| 363 |
+
"firefox": "57",
|
| 364 |
+
"safari": "12",
|
| 365 |
+
"node": "10",
|
| 366 |
+
"deno": "1",
|
| 367 |
+
"ios": "12",
|
| 368 |
+
"samsung": "8",
|
| 369 |
+
"opera_mobile": "46",
|
| 370 |
+
"electron": "3.0"
|
| 371 |
+
},
|
| 372 |
+
"proposal-async-generator-functions": {
|
| 373 |
+
"chrome": "63",
|
| 374 |
+
"opera": "50",
|
| 375 |
+
"edge": "79",
|
| 376 |
+
"firefox": "57",
|
| 377 |
+
"safari": "12",
|
| 378 |
+
"node": "10",
|
| 379 |
+
"deno": "1",
|
| 380 |
+
"ios": "12",
|
| 381 |
+
"samsung": "8",
|
| 382 |
+
"opera_mobile": "46",
|
| 383 |
+
"electron": "3.0"
|
| 384 |
+
},
|
| 385 |
+
"transform-object-rest-spread": {
|
| 386 |
+
"chrome": "60",
|
| 387 |
+
"opera": "47",
|
| 388 |
+
"edge": "79",
|
| 389 |
+
"firefox": "55",
|
| 390 |
+
"safari": "11.1",
|
| 391 |
+
"node": "8.3",
|
| 392 |
+
"deno": "1",
|
| 393 |
+
"ios": "11.3",
|
| 394 |
+
"samsung": "8",
|
| 395 |
+
"opera_mobile": "44",
|
| 396 |
+
"electron": "2.0"
|
| 397 |
+
},
|
| 398 |
+
"proposal-object-rest-spread": {
|
| 399 |
+
"chrome": "60",
|
| 400 |
+
"opera": "47",
|
| 401 |
+
"edge": "79",
|
| 402 |
+
"firefox": "55",
|
| 403 |
+
"safari": "11.1",
|
| 404 |
+
"node": "8.3",
|
| 405 |
+
"deno": "1",
|
| 406 |
+
"ios": "11.3",
|
| 407 |
+
"samsung": "8",
|
| 408 |
+
"opera_mobile": "44",
|
| 409 |
+
"electron": "2.0"
|
| 410 |
+
},
|
| 411 |
+
"transform-dotall-regex": {
|
| 412 |
+
"chrome": "62",
|
| 413 |
+
"opera": "49",
|
| 414 |
+
"edge": "79",
|
| 415 |
+
"firefox": "78",
|
| 416 |
+
"safari": "11.1",
|
| 417 |
+
"node": "8.10",
|
| 418 |
+
"deno": "1",
|
| 419 |
+
"ios": "11.3",
|
| 420 |
+
"samsung": "8",
|
| 421 |
+
"rhino": "1.7.15",
|
| 422 |
+
"opera_mobile": "46",
|
| 423 |
+
"electron": "3.0"
|
| 424 |
+
},
|
| 425 |
+
"transform-unicode-property-regex": {
|
| 426 |
+
"chrome": "64",
|
| 427 |
+
"opera": "51",
|
| 428 |
+
"edge": "79",
|
| 429 |
+
"firefox": "78",
|
| 430 |
+
"safari": "11.1",
|
| 431 |
+
"node": "10",
|
| 432 |
+
"deno": "1",
|
| 433 |
+
"ios": "11.3",
|
| 434 |
+
"samsung": "9",
|
| 435 |
+
"opera_mobile": "47",
|
| 436 |
+
"electron": "3.0"
|
| 437 |
+
},
|
| 438 |
+
"proposal-unicode-property-regex": {
|
| 439 |
+
"chrome": "64",
|
| 440 |
+
"opera": "51",
|
| 441 |
+
"edge": "79",
|
| 442 |
+
"firefox": "78",
|
| 443 |
+
"safari": "11.1",
|
| 444 |
+
"node": "10",
|
| 445 |
+
"deno": "1",
|
| 446 |
+
"ios": "11.3",
|
| 447 |
+
"samsung": "9",
|
| 448 |
+
"opera_mobile": "47",
|
| 449 |
+
"electron": "3.0"
|
| 450 |
+
},
|
| 451 |
+
"transform-named-capturing-groups-regex": {
|
| 452 |
+
"chrome": "64",
|
| 453 |
+
"opera": "51",
|
| 454 |
+
"edge": "79",
|
| 455 |
+
"firefox": "78",
|
| 456 |
+
"safari": "11.1",
|
| 457 |
+
"node": "10",
|
| 458 |
+
"deno": "1",
|
| 459 |
+
"ios": "11.3",
|
| 460 |
+
"samsung": "9",
|
| 461 |
+
"opera_mobile": "47",
|
| 462 |
+
"electron": "3.0"
|
| 463 |
+
},
|
| 464 |
+
"transform-async-to-generator": {
|
| 465 |
+
"chrome": "55",
|
| 466 |
+
"opera": "42",
|
| 467 |
+
"edge": "15",
|
| 468 |
+
"firefox": "52",
|
| 469 |
+
"safari": "11",
|
| 470 |
+
"node": "7.6",
|
| 471 |
+
"deno": "1",
|
| 472 |
+
"ios": "11",
|
| 473 |
+
"samsung": "6",
|
| 474 |
+
"opera_mobile": "42",
|
| 475 |
+
"electron": "1.6"
|
| 476 |
+
},
|
| 477 |
+
"transform-exponentiation-operator": {
|
| 478 |
+
"chrome": "52",
|
| 479 |
+
"opera": "39",
|
| 480 |
+
"edge": "14",
|
| 481 |
+
"firefox": "52",
|
| 482 |
+
"safari": "10.1",
|
| 483 |
+
"node": "7",
|
| 484 |
+
"deno": "1",
|
| 485 |
+
"ios": "10.3",
|
| 486 |
+
"samsung": "6",
|
| 487 |
+
"rhino": "1.7.14",
|
| 488 |
+
"opera_mobile": "41",
|
| 489 |
+
"electron": "1.3"
|
| 490 |
+
},
|
| 491 |
+
"transform-template-literals": {
|
| 492 |
+
"chrome": "41",
|
| 493 |
+
"opera": "28",
|
| 494 |
+
"edge": "13",
|
| 495 |
+
"firefox": "34",
|
| 496 |
+
"safari": "13",
|
| 497 |
+
"node": "4",
|
| 498 |
+
"deno": "1",
|
| 499 |
+
"ios": "13",
|
| 500 |
+
"samsung": "3.4",
|
| 501 |
+
"opera_mobile": "28",
|
| 502 |
+
"electron": "0.21"
|
| 503 |
+
},
|
| 504 |
+
"transform-literals": {
|
| 505 |
+
"chrome": "44",
|
| 506 |
+
"opera": "31",
|
| 507 |
+
"edge": "12",
|
| 508 |
+
"firefox": "53",
|
| 509 |
+
"safari": "9",
|
| 510 |
+
"node": "4",
|
| 511 |
+
"deno": "1",
|
| 512 |
+
"ios": "9",
|
| 513 |
+
"samsung": "4",
|
| 514 |
+
"rhino": "1.7.15",
|
| 515 |
+
"opera_mobile": "32",
|
| 516 |
+
"electron": "0.30"
|
| 517 |
+
},
|
| 518 |
+
"transform-function-name": {
|
| 519 |
+
"chrome": "51",
|
| 520 |
+
"opera": "38",
|
| 521 |
+
"edge": "79",
|
| 522 |
+
"firefox": "53",
|
| 523 |
+
"safari": "10",
|
| 524 |
+
"node": "6.5",
|
| 525 |
+
"deno": "1",
|
| 526 |
+
"ios": "10",
|
| 527 |
+
"samsung": "5",
|
| 528 |
+
"opera_mobile": "41",
|
| 529 |
+
"electron": "1.2"
|
| 530 |
+
},
|
| 531 |
+
"transform-arrow-functions": {
|
| 532 |
+
"chrome": "47",
|
| 533 |
+
"opera": "34",
|
| 534 |
+
"edge": "13",
|
| 535 |
+
"firefox": "43",
|
| 536 |
+
"safari": "10",
|
| 537 |
+
"node": "6",
|
| 538 |
+
"deno": "1",
|
| 539 |
+
"ios": "10",
|
| 540 |
+
"samsung": "5",
|
| 541 |
+
"rhino": "1.7.13",
|
| 542 |
+
"opera_mobile": "34",
|
| 543 |
+
"electron": "0.36"
|
| 544 |
+
},
|
| 545 |
+
"transform-block-scoped-functions": {
|
| 546 |
+
"chrome": "41",
|
| 547 |
+
"opera": "28",
|
| 548 |
+
"edge": "12",
|
| 549 |
+
"firefox": "46",
|
| 550 |
+
"safari": "10",
|
| 551 |
+
"node": "4",
|
| 552 |
+
"deno": "1",
|
| 553 |
+
"ie": "11",
|
| 554 |
+
"ios": "10",
|
| 555 |
+
"samsung": "3.4",
|
| 556 |
+
"opera_mobile": "28",
|
| 557 |
+
"electron": "0.21"
|
| 558 |
+
},
|
| 559 |
+
"transform-classes": {
|
| 560 |
+
"chrome": "46",
|
| 561 |
+
"opera": "33",
|
| 562 |
+
"edge": "13",
|
| 563 |
+
"firefox": "45",
|
| 564 |
+
"safari": "10",
|
| 565 |
+
"node": "5",
|
| 566 |
+
"deno": "1",
|
| 567 |
+
"ios": "10",
|
| 568 |
+
"samsung": "5",
|
| 569 |
+
"opera_mobile": "33",
|
| 570 |
+
"electron": "0.36"
|
| 571 |
+
},
|
| 572 |
+
"transform-object-super": {
|
| 573 |
+
"chrome": "46",
|
| 574 |
+
"opera": "33",
|
| 575 |
+
"edge": "13",
|
| 576 |
+
"firefox": "45",
|
| 577 |
+
"safari": "10",
|
| 578 |
+
"node": "5",
|
| 579 |
+
"deno": "1",
|
| 580 |
+
"ios": "10",
|
| 581 |
+
"samsung": "5",
|
| 582 |
+
"opera_mobile": "33",
|
| 583 |
+
"electron": "0.36"
|
| 584 |
+
},
|
| 585 |
+
"transform-shorthand-properties": {
|
| 586 |
+
"chrome": "43",
|
| 587 |
+
"opera": "30",
|
| 588 |
+
"edge": "12",
|
| 589 |
+
"firefox": "33",
|
| 590 |
+
"safari": "9",
|
| 591 |
+
"node": "4",
|
| 592 |
+
"deno": "1",
|
| 593 |
+
"ios": "9",
|
| 594 |
+
"samsung": "4",
|
| 595 |
+
"rhino": "1.7.14",
|
| 596 |
+
"opera_mobile": "30",
|
| 597 |
+
"electron": "0.27"
|
| 598 |
+
},
|
| 599 |
+
"transform-duplicate-keys": {
|
| 600 |
+
"chrome": "42",
|
| 601 |
+
"opera": "29",
|
| 602 |
+
"edge": "12",
|
| 603 |
+
"firefox": "34",
|
| 604 |
+
"safari": "9",
|
| 605 |
+
"node": "4",
|
| 606 |
+
"deno": "1",
|
| 607 |
+
"ios": "9",
|
| 608 |
+
"samsung": "3.4",
|
| 609 |
+
"opera_mobile": "29",
|
| 610 |
+
"electron": "0.25"
|
| 611 |
+
},
|
| 612 |
+
"transform-computed-properties": {
|
| 613 |
+
"chrome": "44",
|
| 614 |
+
"opera": "31",
|
| 615 |
+
"edge": "12",
|
| 616 |
+
"firefox": "34",
|
| 617 |
+
"safari": "7.1",
|
| 618 |
+
"node": "4",
|
| 619 |
+
"deno": "1",
|
| 620 |
+
"ios": "8",
|
| 621 |
+
"samsung": "4",
|
| 622 |
+
"rhino": "1.8",
|
| 623 |
+
"opera_mobile": "32",
|
| 624 |
+
"electron": "0.30"
|
| 625 |
+
},
|
| 626 |
+
"transform-for-of": {
|
| 627 |
+
"chrome": "51",
|
| 628 |
+
"opera": "38",
|
| 629 |
+
"edge": "15",
|
| 630 |
+
"firefox": "53",
|
| 631 |
+
"safari": "10",
|
| 632 |
+
"node": "6.5",
|
| 633 |
+
"deno": "1",
|
| 634 |
+
"ios": "10",
|
| 635 |
+
"samsung": "5",
|
| 636 |
+
"opera_mobile": "41",
|
| 637 |
+
"electron": "1.2"
|
| 638 |
+
},
|
| 639 |
+
"transform-sticky-regex": {
|
| 640 |
+
"chrome": "49",
|
| 641 |
+
"opera": "36",
|
| 642 |
+
"edge": "13",
|
| 643 |
+
"firefox": "3",
|
| 644 |
+
"safari": "10",
|
| 645 |
+
"node": "6",
|
| 646 |
+
"deno": "1",
|
| 647 |
+
"ios": "10",
|
| 648 |
+
"samsung": "5",
|
| 649 |
+
"rhino": "1.7.15",
|
| 650 |
+
"opera_mobile": "36",
|
| 651 |
+
"electron": "0.37"
|
| 652 |
+
},
|
| 653 |
+
"transform-unicode-escapes": {
|
| 654 |
+
"chrome": "44",
|
| 655 |
+
"opera": "31",
|
| 656 |
+
"edge": "12",
|
| 657 |
+
"firefox": "53",
|
| 658 |
+
"safari": "9",
|
| 659 |
+
"node": "4",
|
| 660 |
+
"deno": "1",
|
| 661 |
+
"ios": "9",
|
| 662 |
+
"samsung": "4",
|
| 663 |
+
"rhino": "1.7.15",
|
| 664 |
+
"opera_mobile": "32",
|
| 665 |
+
"electron": "0.30"
|
| 666 |
+
},
|
| 667 |
+
"transform-unicode-regex": {
|
| 668 |
+
"chrome": "50",
|
| 669 |
+
"opera": "37",
|
| 670 |
+
"edge": "13",
|
| 671 |
+
"firefox": "46",
|
| 672 |
+
"safari": "12",
|
| 673 |
+
"node": "6",
|
| 674 |
+
"deno": "1",
|
| 675 |
+
"ios": "12",
|
| 676 |
+
"samsung": "5",
|
| 677 |
+
"opera_mobile": "37",
|
| 678 |
+
"electron": "1.1"
|
| 679 |
+
},
|
| 680 |
+
"transform-spread": {
|
| 681 |
+
"chrome": "46",
|
| 682 |
+
"opera": "33",
|
| 683 |
+
"edge": "13",
|
| 684 |
+
"firefox": "45",
|
| 685 |
+
"safari": "10",
|
| 686 |
+
"node": "5",
|
| 687 |
+
"deno": "1",
|
| 688 |
+
"ios": "10",
|
| 689 |
+
"samsung": "5",
|
| 690 |
+
"opera_mobile": "33",
|
| 691 |
+
"electron": "0.36"
|
| 692 |
+
},
|
| 693 |
+
"transform-destructuring": {
|
| 694 |
+
"chrome": "51",
|
| 695 |
+
"opera": "38",
|
| 696 |
+
"edge": "15",
|
| 697 |
+
"firefox": "53",
|
| 698 |
+
"safari": "10",
|
| 699 |
+
"node": "6.5",
|
| 700 |
+
"deno": "1",
|
| 701 |
+
"ios": "10",
|
| 702 |
+
"samsung": "5",
|
| 703 |
+
"opera_mobile": "41",
|
| 704 |
+
"electron": "1.2"
|
| 705 |
+
},
|
| 706 |
+
"transform-block-scoping": {
|
| 707 |
+
"chrome": "50",
|
| 708 |
+
"opera": "37",
|
| 709 |
+
"edge": "14",
|
| 710 |
+
"firefox": "53",
|
| 711 |
+
"safari": "11",
|
| 712 |
+
"node": "6",
|
| 713 |
+
"deno": "1",
|
| 714 |
+
"ios": "11",
|
| 715 |
+
"samsung": "5",
|
| 716 |
+
"opera_mobile": "37",
|
| 717 |
+
"electron": "1.1"
|
| 718 |
+
},
|
| 719 |
+
"transform-typeof-symbol": {
|
| 720 |
+
"chrome": "48",
|
| 721 |
+
"opera": "35",
|
| 722 |
+
"edge": "12",
|
| 723 |
+
"firefox": "36",
|
| 724 |
+
"safari": "9",
|
| 725 |
+
"node": "6",
|
| 726 |
+
"deno": "1",
|
| 727 |
+
"ios": "9",
|
| 728 |
+
"samsung": "5",
|
| 729 |
+
"rhino": "1.8",
|
| 730 |
+
"opera_mobile": "35",
|
| 731 |
+
"electron": "0.37"
|
| 732 |
+
},
|
| 733 |
+
"transform-new-target": {
|
| 734 |
+
"chrome": "46",
|
| 735 |
+
"opera": "33",
|
| 736 |
+
"edge": "14",
|
| 737 |
+
"firefox": "41",
|
| 738 |
+
"safari": "10",
|
| 739 |
+
"node": "5",
|
| 740 |
+
"deno": "1",
|
| 741 |
+
"ios": "10",
|
| 742 |
+
"samsung": "5",
|
| 743 |
+
"opera_mobile": "33",
|
| 744 |
+
"electron": "0.36"
|
| 745 |
+
},
|
| 746 |
+
"transform-regenerator": {
|
| 747 |
+
"chrome": "50",
|
| 748 |
+
"opera": "37",
|
| 749 |
+
"edge": "13",
|
| 750 |
+
"firefox": "53",
|
| 751 |
+
"safari": "10",
|
| 752 |
+
"node": "6",
|
| 753 |
+
"deno": "1",
|
| 754 |
+
"ios": "10",
|
| 755 |
+
"samsung": "5",
|
| 756 |
+
"opera_mobile": "37",
|
| 757 |
+
"electron": "1.1"
|
| 758 |
+
},
|
| 759 |
+
"transform-member-expression-literals": {
|
| 760 |
+
"chrome": "7",
|
| 761 |
+
"opera": "12",
|
| 762 |
+
"edge": "12",
|
| 763 |
+
"firefox": "2",
|
| 764 |
+
"safari": "5.1",
|
| 765 |
+
"node": "0.4",
|
| 766 |
+
"deno": "1",
|
| 767 |
+
"ie": "9",
|
| 768 |
+
"android": "4",
|
| 769 |
+
"ios": "6",
|
| 770 |
+
"phantom": "1.9",
|
| 771 |
+
"samsung": "1",
|
| 772 |
+
"rhino": "1.7.13",
|
| 773 |
+
"opera_mobile": "12",
|
| 774 |
+
"electron": "0.20"
|
| 775 |
+
},
|
| 776 |
+
"transform-property-literals": {
|
| 777 |
+
"chrome": "7",
|
| 778 |
+
"opera": "12",
|
| 779 |
+
"edge": "12",
|
| 780 |
+
"firefox": "2",
|
| 781 |
+
"safari": "5.1",
|
| 782 |
+
"node": "0.4",
|
| 783 |
+
"deno": "1",
|
| 784 |
+
"ie": "9",
|
| 785 |
+
"android": "4",
|
| 786 |
+
"ios": "6",
|
| 787 |
+
"phantom": "1.9",
|
| 788 |
+
"samsung": "1",
|
| 789 |
+
"rhino": "1.7.13",
|
| 790 |
+
"opera_mobile": "12",
|
| 791 |
+
"electron": "0.20"
|
| 792 |
+
},
|
| 793 |
+
"transform-reserved-words": {
|
| 794 |
+
"chrome": "13",
|
| 795 |
+
"opera": "10.50",
|
| 796 |
+
"edge": "12",
|
| 797 |
+
"firefox": "2",
|
| 798 |
+
"safari": "3.1",
|
| 799 |
+
"node": "0.6",
|
| 800 |
+
"deno": "1",
|
| 801 |
+
"ie": "9",
|
| 802 |
+
"android": "4.4",
|
| 803 |
+
"ios": "6",
|
| 804 |
+
"phantom": "1.9",
|
| 805 |
+
"samsung": "1",
|
| 806 |
+
"rhino": "1.7.13",
|
| 807 |
+
"opera_mobile": "10.1",
|
| 808 |
+
"electron": "0.20"
|
| 809 |
+
},
|
| 810 |
+
"transform-export-namespace-from": {
|
| 811 |
+
"chrome": "72",
|
| 812 |
+
"deno": "1.0",
|
| 813 |
+
"edge": "79",
|
| 814 |
+
"firefox": "80",
|
| 815 |
+
"node": "13.2.0",
|
| 816 |
+
"opera": "60",
|
| 817 |
+
"opera_mobile": "51",
|
| 818 |
+
"safari": "14.1",
|
| 819 |
+
"ios": "14.5",
|
| 820 |
+
"samsung": "11.0",
|
| 821 |
+
"android": "72",
|
| 822 |
+
"electron": "5.0"
|
| 823 |
+
},
|
| 824 |
+
"proposal-export-namespace-from": {
|
| 825 |
+
"chrome": "72",
|
| 826 |
+
"deno": "1.0",
|
| 827 |
+
"edge": "79",
|
| 828 |
+
"firefox": "80",
|
| 829 |
+
"node": "13.2.0",
|
| 830 |
+
"opera": "60",
|
| 831 |
+
"opera_mobile": "51",
|
| 832 |
+
"safari": "14.1",
|
| 833 |
+
"ios": "14.5",
|
| 834 |
+
"samsung": "11.0",
|
| 835 |
+
"android": "72",
|
| 836 |
+
"electron": "5.0"
|
| 837 |
+
}
|
| 838 |
+
}
|
dashboard/frontend/node_modules/@babel/compat-data/native-modules.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
|
| 2 |
+
module.exports = require("./data/native-modules.json");
|
dashboard/frontend/node_modules/@babel/compat-data/overlapping-plugins.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
|
| 2 |
+
module.exports = require("./data/overlapping-plugins.json");
|
dashboard/frontend/node_modules/@babel/compat-data/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "@babel/compat-data",
|
| 3 |
+
"version": "7.29.0",
|
| 4 |
+
"author": "The Babel Team (https://babel.dev/team)",
|
| 5 |
+
"license": "MIT",
|
| 6 |
+
"description": "The compat-data to determine required Babel plugins",
|
| 7 |
+
"repository": {
|
| 8 |
+
"type": "git",
|
| 9 |
+
"url": "https://github.com/babel/babel.git",
|
| 10 |
+
"directory": "packages/babel-compat-data"
|
| 11 |
+
},
|
| 12 |
+
"publishConfig": {
|
| 13 |
+
"access": "public"
|
| 14 |
+
},
|
| 15 |
+
"exports": {
|
| 16 |
+
"./plugins": "./plugins.js",
|
| 17 |
+
"./native-modules": "./native-modules.js",
|
| 18 |
+
"./corejs2-built-ins": "./corejs2-built-ins.js",
|
| 19 |
+
"./corejs3-shipped-proposals": "./corejs3-shipped-proposals.js",
|
| 20 |
+
"./overlapping-plugins": "./overlapping-plugins.js",
|
| 21 |
+
"./plugin-bugfixes": "./plugin-bugfixes.js"
|
| 22 |
+
},
|
| 23 |
+
"scripts": {
|
| 24 |
+
"build-data": "./scripts/download-compat-table.sh && node ./scripts/build-data.mjs && node ./scripts/build-modules-support.mjs && node ./scripts/build-bugfixes-targets.mjs"
|
| 25 |
+
},
|
| 26 |
+
"keywords": [
|
| 27 |
+
"babel",
|
| 28 |
+
"compat-table",
|
| 29 |
+
"compat-data"
|
| 30 |
+
],
|
| 31 |
+
"devDependencies": {
|
| 32 |
+
"@mdn/browser-compat-data": "^6.0.8",
|
| 33 |
+
"core-js-compat": "^3.48.0",
|
| 34 |
+
"electron-to-chromium": "^1.5.278"
|
| 35 |
+
},
|
| 36 |
+
"engines": {
|
| 37 |
+
"node": ">=6.9.0"
|
| 38 |
+
},
|
| 39 |
+
"type": "commonjs"
|
| 40 |
+
}
|
dashboard/frontend/node_modules/@babel/compat-data/plugin-bugfixes.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
|
| 2 |
+
module.exports = require("./data/plugin-bugfixes.json");
|