diff --git a/apps/fractal-inference-swarms/.gitignore b/apps/fractal-inference-swarms/.gitignore new file mode 100644 index 0000000..6cde671 --- /dev/null +++ b/apps/fractal-inference-swarms/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +.next/ +.env +.env.local +*.log +logs/ +.DS_Store \ No newline at end of file diff --git a/apps/fractal-inference-swarms/LICENSE.md b/apps/fractal-inference-swarms/LICENSE.md new file mode 100644 index 0000000..63082f4 --- /dev/null +++ b/apps/fractal-inference-swarms/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Cortensor Community + +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: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +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. diff --git a/apps/fractal-inference-swarms/PROJECTS.yml b/apps/fractal-inference-swarms/PROJECTS.yml new file mode 100644 index 0000000..501df36 --- /dev/null +++ b/apps/fractal-inference-swarms/PROJECTS.yml @@ -0,0 +1,16 @@ +- name: fractal-inference-swarms + category: apps + owner: "@your-github-username" + collaborators: + - "@your-discord#0001" + status: active + created: 2026-02-11 + version: v0.1.0 + tags: + - cortensor + - orchestration + - ai-swarms + - pouw + - validators + - x402 + - observability diff --git a/apps/fractal-inference-swarms/README.md b/apps/fractal-inference-swarms/README.md new file mode 100644 index 0000000..f0b2657 --- /dev/null +++ b/apps/fractal-inference-swarms/README.md @@ -0,0 +1,79 @@ +Fractal Inference Swarms (FIS) +Cortensor-Native AI Orchestration Infrastructure +A production-grade distributed inference system that decomposes complex tasks, routes them through Cortensor nodes, validates outputs, and distributes micropayments based on performance. +🎯 Project OverviewFractal Inference Swarms is a Kubernetes-like orchestration layer for decentralized AI inference. It sits on top of Cortensor and provides: +Intelligent Task Decomposition - Break complex prompts into atomic subtasks +Distributed Inference Routing - Route subtasks to multiple Cortensor nodes +Validation & Consensus - Validate outputs using Cortensor validators +Result Aggregation - Merge validated outputs into unified responses +PoUW-Based Scoring - Rank node performance using multi-factor metrics +Micropayment Distribution - Reward high-performing nodes with x402 tokens +Real-Time Observability - Monitor the entire swarm lifecycle via dashboard +This is NOT a prototype. This is enterprise-grade AI infrastructure designed for production workloads.πŸ—οΈ Architectureβ”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ User Interface β”‚ +β”‚ (Next.js Real-Time Dashboard) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Fractal Orchestrator β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Task Splitterβ”‚ β”‚ Merge Engine β”‚ β”‚ Scoring Engine β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Validation β”‚ β”‚Reward Engine β”‚ β”‚ WebSocket Serverβ”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Cortensor Network β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Node A β”‚ β”‚ Node B β”‚ β”‚ Node C β”‚ β”‚ Node D β”‚ ... β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Cortensor Validators (Consensus) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Mock x402 Payment Ledger β”‚ +β”‚ (Ready for Real Blockchain Integration) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜βœ¨ Key Features🧠 Intelligent Orchestration + +Automatic task decomposition into optimized subtasks +Dynamic subtask routing to available Cortensor nodes +State machine tracking (CREATED β†’ SPLIT β†’ ROUTED β†’ VALIDATING β†’ MERGED β†’ FINALIZED) +βœ… Validation & Consensus + +Cross-validation of outputs from multiple nodes +Cortensor validator consensus integration +Conflict resolution using confidence-weighted voting +Quality gates to reject low-confidence results +πŸ”€ Result Merging + +Synthesizes distributed outputs into coherent final result +Contribution mapping (tracks which node provided what) +Confidence scoring for merged output +Deduplication and format normalization +πŸ“Š PoUW-Based Scoring +Final Score = w₁×consensus + wβ‚‚Γ—confidence + w₃×speed + wβ‚„Γ—reliability +Configurable weights via environment variables +Multi-dimensional node evaluation +Real-time leaderboard updates +πŸ’° Economic Layer + +Mock x402 micropayment distribution +reward = baseReward Γ— normalizedScore +Complete transaction ledger with audit trail +Stake simulation (increases/slashing based on performance) +πŸ“‘ Real-Time Dashboard + +WebSocket-powered live updates +Agent spawn animations +Session lifecycle visualization +Validator consensus progress bars +Leaderboard and reward distribution charts \ No newline at end of file diff --git a/apps/fractal-inference-swarms/RELEASE.md b/apps/fractal-inference-swarms/RELEASE.md new file mode 100644 index 0000000..8854c39 --- /dev/null +++ b/apps/fractal-inference-swarms/RELEASE.md @@ -0,0 +1,8 @@ +# Release Notes + +## v0.1.0 - Initial Release +- Cortensor-native swarm orchestration +- Real-time dashboard +- PoUW scoring engine +- x402 mock reward ledger +- Observability metrics diff --git a/apps/fractal-inference-swarms/STATUS.md b/apps/fractal-inference-swarms/STATUS.md new file mode 100644 index 0000000..0c0b7bb --- /dev/null +++ b/apps/fractal-inference-swarms/STATUS.md @@ -0,0 +1,25 @@ +# Project Status + +## Current Version +v0.1.0-beta + +## Completed +- Task orchestration +- Cortensor session integration +- Validator consensus integration +- PoUW scoring +- x402 mock rewards +- Dashboard visualization + +## In Progress +- On-chain artifact storage +- Stake slashing logic + +## Planned +- ERC-8004 artifact support +- Real Cortensor mainnet integration +- Benchmark suite + +## Known Issues +- Simulated latency variability +- Limited validator diversity in demo mode diff --git a/apps/fractal-inference-swarms/backend/.dockerignore b/apps/fractal-inference-swarms/backend/.dockerignore new file mode 100644 index 0000000..e7effbb --- /dev/null +++ b/apps/fractal-inference-swarms/backend/.dockerignore @@ -0,0 +1,7 @@ +node_modules +dist +logs +.env +*.log +.git +.gitignore \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/.env.example b/apps/fractal-inference-swarms/backend/.env.example new file mode 100644 index 0000000..4f0fe5d --- /dev/null +++ b/apps/fractal-inference-swarms/backend/.env.example @@ -0,0 +1,31 @@ +# Server Configuration +PORT=3001 +NODE_ENV=development +HOST=0.0.0.0 + +# CORS +CORS_ORIGIN=http://localhost:3000 + +# WebSocket +WS_PORT=3002 +WS_HEARTBEAT_INTERVAL=30000 + +# Orchestrator Configuration +MAX_CONCURRENT_AGENTS=20 +DEFAULT_SWARM_SIZE=5 +AGENT_TIMEOUT_MS=30000 +TASK_SPLIT_STRATEGY=semantic + +# Scoring Engine Weights +SCORING_WEIGHT_CONFIDENCE=0.4 +SCORING_WEIGHT_SPEED=0.3 +SCORING_WEIGHT_RELIABILITY=0.3 + +# Reward Engine +BASE_REWARD_TOKENS=100 +MIN_SCORE_THRESHOLD=0.3 +REWARD_POOL_SIZE=1000 + +# Logging +LOG_LEVEL=debug +LOG_FORMAT=json \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/Dockerfile b/apps/fractal-inference-swarms/backend/Dockerfile new file mode 100644 index 0000000..97438e1 --- /dev/null +++ b/apps/fractal-inference-swarms/backend/Dockerfile @@ -0,0 +1,37 @@ +FROM node:20-alpine AS builder + +WORKDIR /app + +COPY package.json package-lock.json* ./ +RUN npm ci --ignore-scripts + +COPY tsconfig.json ./ +COPY src ./src + +RUN npm run build + +FROM node:20-alpine AS runner + +WORKDIR /app + +ENV NODE_ENV=production + +RUN addgroup --system --gid 1001 swarm && \ + adduser --system --uid 1001 agent + +COPY --from=builder /app/package.json ./ +COPY --from=builder /app/package-lock.json* ./ +RUN npm ci --omit=dev --ignore-scripts + +COPY --from=builder /app/dist ./dist + +RUN mkdir -p logs && chown -R agent:swarm logs + +USER agent + +EXPOSE 3001 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3001/api/health || exit 1 + +CMD ["node", "dist/server.js"] \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/package-lock.json b/apps/fractal-inference-swarms/backend/package-lock.json new file mode 100644 index 0000000..729cde1 --- /dev/null +++ b/apps/fractal-inference-swarms/backend/package-lock.json @@ -0,0 +1,2138 @@ +{ + "name": "fractal-inference-swarms-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fractal-inference-swarms-backend", + "version": "1.0.0", + "dependencies": { + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.21.0", + "helmet": "^7.1.0", + "morgan": "^1.10.0", + "uuid": "^9.0.1", + "winston": "^3.13.0", + "ws": "^8.17.0" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/morgan": "^1.9.9", + "@types/node": "^20.14.0", + "@types/uuid": "^9.0.8", + "@types/ws": "^8.5.10", + "ts-node-dev": "^2.0.0", + "typescript": "^5.4.5" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "license": "MIT", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/morgan": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz", + "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.2.0.tgz", + "integrity": "sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node-dev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", + "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^10.4.0", + "tsconfig": "^7.0.0" + }, + "bin": { + "ts-node-dev": "lib/bin.js", + "tsnd": "lib/bin.js" + }, + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "node-notifier": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/apps/fractal-inference-swarms/backend/package.json b/apps/fractal-inference-swarms/backend/package.json new file mode 100644 index 0000000..06541a5 --- /dev/null +++ b/apps/fractal-inference-swarms/backend/package.json @@ -0,0 +1,32 @@ +{ + "name": "fractal-inference-swarms-backend", + "version": "1.0.0", + "description": "AI-native orchestration system with fractal agent swarms and x402 micropayments", + "main": "dist/server.js", + "scripts": { + "dev": "ts-node-dev --respawn --transpile-only src/server.ts", + "build": "tsc", + "start": "node dist/server.js", + "lint": "eslint src/**/*.ts" + }, + "dependencies": { + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.21.0", + "helmet": "^7.1.0", + "morgan": "^1.10.0", + "uuid": "^9.0.1", + "ws": "^8.17.0", + "winston": "^3.13.0" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/morgan": "^1.9.9", + "@types/node": "^20.14.0", + "@types/uuid": "^9.0.8", + "@types/ws": "^8.5.10", + "ts-node-dev": "^2.0.0", + "typescript": "^5.4.5" + } + } \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/agents/Orchestrator.ts b/apps/fractal-inference-swarms/backend/src/agents/Orchestrator.ts new file mode 100644 index 0000000..0ae27c7 --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/agents/Orchestrator.ts @@ -0,0 +1,312 @@ +import { v4 as uuidv4 } from 'uuid'; +import { Task, TaskCreateRequest, createTask, MergedOutput } from '../models/Task'; +import { Agent, AgentResult, ScoredResult, RewardTransaction, SwarmSession, SwarmMetrics } from '../models/AgentResult'; +import { taskSplitter } from '../engine/TaskSplitter'; +import { scoringEngine } from '../engine/ScoringEngine'; +import { rewardEngine } from '../engine/RewardEngine'; +import { mergeEngine, MergeStrategy } from '../engine/MergeEngine'; +import { swarmAgentRunner } from './SwarmAgent'; +import { x402Engine } from '../payments/X402Mock'; +import { eventBus } from '../utils/EventBus'; +import { config } from '../config'; +import { createChildLogger } from '../utils/logger'; + +const logger = createChildLogger('Orchestrator'); + +const ORCHESTRATOR_WALLET = 'orchestrator-treasury-0x000'; + +export class Orchestrator { + private tasks: Map = new Map(); + private sessions: Map = new Map(); + private allRewards: RewardTransaction[] = []; + private startTime: number = Date.now(); + private totalAgentsSpawned: number = 0; + private totalTasksCompleted: number = 0; + private totalTokensDistributed: number = 0; + + async submitTask(request: TaskCreateRequest): Promise { + const task = createTask(request, config.orchestrator.defaultSwarmSize); + this.tasks.set(task.id, task); + + logger.info('Task submitted', { + taskId: task.id, + title: task.title, + swarmSize: task.swarmSize, + }); + + eventBus.emitSwarmEvent('task:created', task.id, { task }); + + this.executeTaskPipeline(task).catch((error) => { + logger.error('Task pipeline failed', { + taskId: task.id, + error: (error as Error).message, + }); + task.status = 'failed'; + task.errorMessage = (error as Error).message; + task.updatedAt = Date.now(); + eventBus.emitSwarmEvent('task:failed', task.id, { task, error: (error as Error).message }); + }); + + return task; + } + + private async executeTaskPipeline(task: Task): Promise { + const pipelineStart = Date.now(); + + // Phase 1: Split task + task.status = 'splitting'; + task.updatedAt = Date.now(); + eventBus.emitSwarmEvent('task:splitting', task.id, { task }); + + const subtasks = taskSplitter.splitTask({ + strategy: config.orchestrator.taskSplitStrategy, + swarmSize: task.swarmSize, + taskDescription: task.description, + taskId: task.id, + }); + + task.subtasks = subtasks; + task.updatedAt = Date.now(); + eventBus.emitSwarmEvent('task:subtasks_ready', task.id, { task, subtasks }); + + // Phase 2: Create swarm session + const session: SwarmSession = { + id: uuidv4(), + taskId: task.id, + agents: [], + results: [], + scoredResults: [], + rewards: [], + status: 'active', + startedAt: Date.now(), + completedAt: null, + totalTokensDistributed: 0, + }; + this.sessions.set(session.id, session); + + // Phase 3: Spawn agents and run inference + task.status = 'spawning_agents'; + task.updatedAt = Date.now(); + eventBus.emitSwarmEvent('task:agents_spawning', task.id, { task, sessionId: session.id }); + + const agentPromises = subtasks.map(async (subtask) => { + try { + subtask.status = 'assigned'; + const { agent, result } = await swarmAgentRunner.spawnAndRun(subtask, task.id); + subtask.assignedAgentId = agent.id; + subtask.status = 'completed'; + subtask.completedAt = Date.now(); + session.agents.push(agent); + session.results.push(result); + this.totalAgentsSpawned++; + return { agent, result, success: true as const }; + } catch (error) { + subtask.status = 'failed'; + logger.error('Agent execution failed for subtask', { + subtaskId: subtask.id, + error: (error as Error).message, + }); + return { success: false as const, error: (error as Error).message }; + } + }); + + task.status = 'inference_running'; + task.updatedAt = Date.now(); + eventBus.emitSwarmEvent('task:inference_started', task.id, { task }); + + const agentResults = await Promise.all(agentPromises); + const successfulResults = agentResults.filter( + (r): r is { agent: Agent; result: AgentResult; success: true } => r.success + ); + + if (successfulResults.length === 0) { + throw new Error('All agents failed β€” no results to score'); + } + + eventBus.emitSwarmEvent('task:inference_complete', task.id, { + task, + resultCount: successfulResults.length, + failedCount: agentResults.length - successfulResults.length, + }); + + // Phase 4: Score results + task.status = 'scoring'; + task.updatedAt = Date.now(); + eventBus.emitSwarmEvent('task:scoring_started', task.id, { task }); + + const scoredResults = scoringEngine.scoreResults(session.results); + session.scoredResults = scoredResults; + + eventBus.emitSwarmEvent('task:scoring_complete', task.id, { + task, + scoredResults, + topAgent: scoredResults[0], + }); + + // Phase 5: Merge and validate outputs + task.status = 'merging'; + task.updatedAt = Date.now(); + eventBus.emitSwarmEvent('task:merging_started', task.id, { task }); + + const mergeStrategy = this.selectMergeStrategy(scoredResults); + const mergedOutput = mergeEngine.mergeResults(scoredResults, task.id, mergeStrategy); + task.mergedOutput = mergedOutput; + session.status = 'merging'; + + eventBus.emitSwarmEvent('task:merge_complete', task.id, { + task, + mergedOutput, + }); + + eventBus.emitSwarmEvent('task:validation_complete', task.id, { + task, + validationPassed: mergedOutput.validationPassed, + validationScore: mergedOutput.validationScore, + validationDetails: mergedOutput.validationDetails, + }); + + // Phase 6: Distribute rewards + task.status = 'distributing_rewards'; + task.updatedAt = Date.now(); + + const rewards = rewardEngine.distributeRewards(scoredResults, task.id); + session.rewards = rewards; + + const paymentBatch = rewards + .filter((r) => r.tokensEarned > 0) + .map((r) => ({ + from: ORCHESTRATOR_WALLET, + to: `agent-wallet-${r.agentId.slice(0, 12)}`, + amount: r.tokensEarned, + })); + + if (paymentBatch.length > 0) { + await x402Engine.processBatchPayments(paymentBatch); + } + + this.allRewards.push(...rewards); + const sessionTokens = rewards.reduce((sum, r) => sum + r.tokensEarned, 0); + session.totalTokensDistributed = sessionTokens; + this.totalTokensDistributed += sessionTokens; + + eventBus.emitSwarmEvent('task:rewards_distributed', task.id, { + task, + rewards, + totalTokensDistributed: sessionTokens, + }); + + // Phase 7: Complete + task.status = 'completed'; + task.completedAt = Date.now(); + task.updatedAt = Date.now(); + task.totalComputeTimeMs = Date.now() - pipelineStart; + session.status = 'completed'; + session.completedAt = Date.now(); + this.totalTasksCompleted++; + + logger.info('Task pipeline complete', { + taskId: task.id, + totalTimeMs: task.totalComputeTimeMs, + agentsUsed: session.agents.length, + tokensDistributed: sessionTokens, + validationPassed: mergedOutput.validationPassed, + }); + + eventBus.emitSwarmEvent('task:completed', task.id, { + task, + session, + mergedOutput, + }); + + eventBus.emitSwarmEvent('metrics:updated', task.id, this.getMetrics()); + } + + private selectMergeStrategy(scoredResults: ScoredResult[]): MergeStrategy { + if (scoredResults.length <= 2) return 'best_pick'; + + const topScore = scoredResults[0].finalScore; + const avgScore = scoredResults.reduce((s, r) => s + r.finalScore, 0) / scoredResults.length; + const dominance = topScore / avgScore; + + if (dominance > 1.5) return 'best_pick'; + if (scoredResults.length >= 5) return 'hierarchical_merge'; + return 'weighted_consensus'; + } + + getTask(taskId: string): Task | undefined { + return this.tasks.get(taskId); + } + + getAllTasks(): Task[] { + return Array.from(this.tasks.values()).sort((a, b) => b.createdAt - a.createdAt); + } + + getSessionForTask(taskId: string): SwarmSession | undefined { + return Array.from(this.sessions.values()).find((s) => s.taskId === taskId); + } + + getAllSessions(): SwarmSession[] { + return Array.from(this.sessions.values()).sort((a, b) => b.startedAt - a.startedAt); + } + + getAgentsForTask(taskId: string): Agent[] { + const session = this.getSessionForTask(taskId); + return session?.agents || []; + } + + getScoredResultsForTask(taskId: string): ScoredResult[] { + const session = this.getSessionForTask(taskId); + return session?.scoredResults || []; + } + + getLedger(): RewardTransaction[] { + return [...this.allRewards].sort((a, b) => b.timestamp - a.timestamp); + } + + getMetrics(): SwarmMetrics { + const sessions = this.getAllSessions(); + const completedSessions = sessions.filter((s) => s.status === 'completed'); + const activeSessions = sessions.filter((s) => s.status === 'active' || s.status === 'scoring' || s.status === 'merging'); + + const avgSwarmSize = + completedSessions.length > 0 + ? completedSessions.reduce((sum, s) => sum + s.agents.length, 0) / completedSessions.length + : 0; + + const avgCompletionTime = + completedSessions.length > 0 + ? completedSessions.reduce((sum, s) => sum + ((s.completedAt || 0) - s.startedAt), 0) / completedSessions.length + : 0; + + const allResults = completedSessions.flatMap((s) => s.scoredResults); + const avgConfidence = + allResults.length > 0 + ? allResults.reduce((sum, r) => sum + r.confidence, 0) / allResults.length + : 0; + + let topAgentId: string | null = null; + let topAgentScore = 0; + for (const result of allResults) { + if (result.finalScore > topAgentScore) { + topAgentScore = result.finalScore; + topAgentId = result.agentId; + } + } + + return { + totalSessions: sessions.length, + activeSessions: activeSessions.length, + totalAgentsSpawned: this.totalAgentsSpawned, + totalTasksCompleted: this.totalTasksCompleted, + totalTokensDistributed: Math.round(this.totalTokensDistributed * 100) / 100, + averageSwarmSize: Math.round(avgSwarmSize * 100) / 100, + averageCompletionTimeMs: Math.round(avgCompletionTime), + averageConfidence: Math.round(avgConfidence * 10000) / 10000, + topAgentId, + topAgentScore: Math.round(topAgentScore * 10000) / 10000, + uptimeMs: Date.now() - this.startTime, + }; + } +} + +export const orchestrator = new Orchestrator(); \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/agents/SwarmAgent.ts b/apps/fractal-inference-swarms/backend/src/agents/SwarmAgent.ts new file mode 100644 index 0000000..9dde16e --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/agents/SwarmAgent.ts @@ -0,0 +1,221 @@ +import { v4 as uuidv4 } from 'uuid'; +import { Agent, AgentResult, InferenceMetadata } from '../models/AgentResult'; +import { SubTask } from '../models/Task'; +import { createChildLogger } from '../utils/logger'; +import { eventBus } from '../utils/EventBus'; + +const logger = createChildLogger('SwarmAgent'); + +const AGENT_NAMES = [ + 'Nexus', 'Cipher', 'Prism', 'Vortex', 'Echo', + 'Quasar', 'Helix', 'Flux', 'Zenith', 'Pulse', + 'Orbit', 'Shard', 'Nova', 'Drift', 'Apex', + 'Arc', 'Blaze', 'Core', 'Delta', 'Enigma', +]; + +const INFERENCE_TEMPLATES: Record = { + analysis: [ + 'After comprehensive analysis of the given parameters, the primary finding indicates that {topic} demonstrates significant correlation with {factor}. The data suggests a {confidence_level} confidence level in this assessment. Key metrics show {metric1} trending {direction} while {metric2} maintains stability. Recommendation: {action}.', + 'Systematic evaluation reveals {count} critical factors influencing {topic}. Primary driver: {factor} (impact coefficient: {coeff}). Secondary influences include environmental variables and temporal dynamics. Confidence in assessment: {confidence_level}. Risk profile: {risk}.', + ], + development: [ + 'Architecture proposal for {topic}: Implement a {pattern} pattern with {layer_count} layers. Core module handles {responsibility1}, while auxiliary services manage {responsibility2}. Estimated complexity: O({complexity}). Scalability factor: {scale_factor}x. Implementation confidence: {confidence_level}.', + 'Technical specification for {topic}: Utilize {technology} framework with {approach} methodology. Module decomposition yields {module_count} independent components. Integration points: {integration_count}. Test coverage target: {coverage}%. Delivery confidence: {confidence_level}.', + ], + research: [ + 'Research findings on {topic}: Literature review of {source_count} sources reveals consensus on {finding}. Divergent viewpoints exist regarding {controversy}. Meta-analysis suggests {conclusion} with p-value < {p_value}. Recommendation for further investigation: {recommendation}.', + 'Investigation into {topic} yields {finding_count} significant findings. Primary conclusion: {conclusion}. Supporting evidence spans {evidence_count} independent sources. Methodology: {methodology}. Confidence interval: {ci_lower}–{ci_upper}. Research quality score: {confidence_level}.', + ], + general: [ + 'Processing subtask for {topic}: Generated output addresses {aspect_count} key aspects. Primary output covers {primary_aspect} with {depth} depth of analysis. Secondary considerations include {secondary_aspects}. Output reliability: {confidence_level}. Compute efficiency: {efficiency}%.', + 'Task completion for {topic}: Addressed all {constraint_count} constraints. Output quality score: {quality_score}/10. Key deliverable: {deliverable}. Verification status: {verification}. Processing iterations: {iterations}. Final confidence: {confidence_level}.', + ], +}; + +function generateInferenceOutput(subtask: SubTask, iterations: number): string { + const categoryKeywords: Record = { + analysis: ['analyze', 'evaluate', 'assess', 'review', 'examine', 'compare'], + development: ['build', 'implement', 'create', 'develop', 'code', 'design'], + research: ['research', 'investigate', 'study', 'explore', 'find', 'discover'], + }; + + let category = 'general'; + const lower = subtask.description.toLowerCase(); + for (const [cat, words] of Object.entries(categoryKeywords)) { + if (words.some((w) => lower.includes(w))) { + category = cat; + break; + } + } + + const templates = INFERENCE_TEMPLATES[category]; + const template = templates[Math.floor(Math.random() * templates.length)]; + const confidence = (0.6 + Math.random() * 0.35).toFixed(4); + + const replacements: Record = { + topic: subtask.description.slice(0, 60), + factor: 'multi-dimensional parameter space', + confidence_level: `${(parseFloat(confidence) * 100).toFixed(1)}%`, + metric1: 'throughput', + metric2: 'latency', + direction: Math.random() > 0.5 ? 'upward' : 'stabilizing', + action: 'proceed with optimized configuration', + count: String(Math.floor(3 + Math.random() * 8)), + coeff: (Math.random() * 0.9 + 0.1).toFixed(3), + risk: Math.random() > 0.5 ? 'moderate' : 'low', + pattern: ['microservice', 'event-driven', 'layered', 'hexagonal'][Math.floor(Math.random() * 4)], + layer_count: String(Math.floor(3 + Math.random() * 4)), + responsibility1: 'core business logic', + responsibility2: 'cross-cutting concerns', + complexity: ['n log n', 'n', 'nΒ²', 'log n'][Math.floor(Math.random() * 4)], + scale_factor: String(Math.floor(2 + Math.random() * 10)), + technology: ['distributed', 'reactive', 'modular', 'cloud-native'][Math.floor(Math.random() * 4)], + approach: ['iterative', 'incremental', 'adaptive'][Math.floor(Math.random() * 3)], + module_count: String(Math.floor(4 + Math.random() * 12)), + integration_count: String(Math.floor(2 + Math.random() * 6)), + coverage: String(Math.floor(80 + Math.random() * 20)), + source_count: String(Math.floor(10 + Math.random() * 50)), + finding: 'convergent optimization patterns', + controversy: 'scalability trade-offs', + conclusion: 'statistically significant improvement', + p_value: (Math.random() * 0.05).toFixed(4), + recommendation: 'expand sample size and extend temporal range', + finding_count: String(Math.floor(3 + Math.random() * 8)), + evidence_count: String(Math.floor(5 + Math.random() * 20)), + methodology: 'systematic multi-source analysis', + ci_lower: (parseFloat(confidence) - 0.1).toFixed(2), + ci_upper: Math.min(1, parseFloat(confidence) + 0.1).toFixed(2), + aspect_count: String(Math.floor(3 + Math.random() * 5)), + primary_aspect: 'core objective fulfillment', + depth: ['thorough', 'comprehensive', 'detailed'][Math.floor(Math.random() * 3)], + secondary_aspects: 'edge cases and boundary conditions', + efficiency: String(Math.floor(70 + Math.random() * 30)), + constraint_count: String(subtask.constraints.length), + quality_score: (7 + Math.random() * 3).toFixed(1), + deliverable: 'structured analytical output', + verification: Math.random() > 0.3 ? 'passed' : 'partial', + iterations: String(iterations), + }; + + let output = template; + for (const [key, value] of Object.entries(replacements)) { + output = output.replace(new RegExp(`\\{${key}\\}`, 'g'), value); + } + + return output; +} + +export class SwarmAgentRunner { + async spawnAndRun(subtask: SubTask, taskId: string): Promise<{ agent: Agent; result: AgentResult }> { + const agentId = uuidv4(); + const agentName = AGENT_NAMES[Math.floor(Math.random() * AGENT_NAMES.length)] + '-' + agentId.slice(0, 6); + + const agent: Agent = { + id: agentId, + taskId, + subtaskId: subtask.id, + name: agentName, + status: 'initializing', + spawnedAt: Date.now(), + startedAt: null, + completedAt: null, + computeTimeMs: 0, + memoryUsageMb: 0, + inferenceIterations: 0, + }; + + logger.info('Agent spawned', { agentId, name: agentName, subtaskId: subtask.id }); + eventBus.emitSwarmEvent('agent:spawned', taskId, { agent }); + + await this.simulateBootup(); + agent.status = 'running'; + agent.startedAt = Date.now(); + eventBus.emitSwarmEvent('agent:started', taskId, { agent }); + + try { + const result = await this.runInference(agent, subtask, taskId); + agent.status = 'completed'; + agent.completedAt = Date.now(); + agent.computeTimeMs = agent.completedAt - agent.startedAt; + + logger.info('Agent completed', { + agentId, + name: agentName, + computeTimeMs: agent.computeTimeMs, + confidence: result.confidence, + }); + + eventBus.emitSwarmEvent('agent:completed', taskId, { agent, result }); + return { agent, result }; + } catch (error) { + agent.status = 'failed'; + agent.completedAt = Date.now(); + agent.computeTimeMs = agent.completedAt - (agent.startedAt || agent.spawnedAt); + + logger.error('Agent failed', { agentId, error: (error as Error).message }); + eventBus.emitSwarmEvent('agent:failed', taskId, { agent, error: (error as Error).message }); + throw error; + } + } + + private async runInference(agent: Agent, subtask: SubTask, taskId: string): Promise { + const iterations = 3 + Math.floor(Math.random() * 8); + const intermediateOutputs: string[] = []; + + for (let i = 0; i < iterations; i++) { + const iterationDelay = 100 + Math.random() * 400; + await new Promise((resolve) => setTimeout(resolve, iterationDelay)); + + const progress = ((i + 1) / iterations) * 100; + agent.inferenceIterations = i + 1; + agent.memoryUsageMb = 128 + Math.random() * 384; + + intermediateOutputs.push(`Iteration ${i + 1}: Processing ${progress.toFixed(0)}% complete`); + + eventBus.emitSwarmEvent('agent:progress', taskId, { + agentId: agent.id, + progress, + iteration: i + 1, + totalIterations: iterations, + memoryUsageMb: agent.memoryUsageMb, + }); + } + + const output = generateInferenceOutput(subtask, iterations); + const confidence = 0.55 + Math.random() * 0.4; + const reliability = 0.6 + Math.random() * 0.35; + const convergenceReached = confidence > 0.7 && reliability > 0.7; + + const metadata: InferenceMetadata = { + modelVersion: `fractal-swarm-v${(1 + Math.random()).toFixed(1)}`, + temperature: 0.3 + Math.random() * 0.5, + topP: 0.85 + Math.random() * 0.14, + iterationsRun: iterations, + convergenceReached, + intermediateOutputs, + }; + + const computeTimeMs = Date.now() - (agent.startedAt || Date.now()); + + return { + id: uuidv4(), + agentId: agent.id, + taskId, + subtaskId: subtask.id, + output, + confidence: Math.round(confidence * 10000) / 10000, + computeTimeMs, + reliabilityScore: Math.round(reliability * 10000) / 10000, + tokenCount: Math.floor(100 + Math.random() * 500), + inferenceMetadata: metadata, + createdAt: Date.now(), + }; + } + + private simulateBootup(): Promise { + const bootTime = 200 + Math.random() * 500; + return new Promise((resolve) => setTimeout(resolve, bootTime)); + } +} + +export const swarmAgentRunner = new SwarmAgentRunner(); \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/api/routes.ts b/apps/fractal-inference-swarms/backend/src/api/routes.ts new file mode 100644 index 0000000..7576b79 --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/api/routes.ts @@ -0,0 +1,189 @@ +import { Router, Request, Response, NextFunction } from 'express'; +import { orchestrator } from '../agents/Orchestrator'; +import { x402Engine } from '../payments/X402Mock'; +import { scoringEngine } from '../engine/ScoringEngine'; +import { TaskCreateRequest } from '../models/Task'; +import { wsServer } from '../websocket/socket'; +import { createChildLogger } from '../utils/logger'; + +const logger = createChildLogger('API'); +const router = Router(); + +function asyncHandler(fn: (req: Request, res: Response, next: NextFunction) => Promise) { + return (req: Request, res: Response, next: NextFunction) => { + fn(req, res, next).catch(next); + }; +} + +// Health check +router.get('/health', (_req: Request, res: Response) => { + res.json({ + status: 'healthy', + service: 'fractal-inference-swarms', + timestamp: Date.now(), + uptime: process.uptime(), + wsConnections: wsServer.getConnectionCount(), + }); +}); + +// Submit a new task +router.post( + '/task', + asyncHandler(async (req: Request, res: Response) => { + const { title, description, priority, swarmSize, metadata } = req.body; + + if (!title || !description) { + res.status(400).json({ + error: 'Validation failed', + details: 'title and description are required', + }); + return; + } + + if (swarmSize && (swarmSize < 2 || swarmSize > 20)) { + res.status(400).json({ + error: 'Validation failed', + details: 'swarmSize must be between 2 and 20', + }); + return; + } + + const request: TaskCreateRequest = { + title, + description, + priority: priority || 'medium', + swarmSize: swarmSize || undefined, + metadata: metadata || {}, + }; + + logger.info('New task submission', { title, priority: request.priority, swarmSize: request.swarmSize }); + const task = await orchestrator.submitTask(request); + + res.status(201).json({ + success: true, + task, + message: 'Task submitted β€” swarm pipeline initiated', + }); + }) +); + +// Get task by ID +router.get('/task/:id', (req: Request, res: Response) => { + const task = orchestrator.getTask(req.params.id); + if (!task) { + res.status(404).json({ error: 'Task not found' }); + return; + } + + const session = orchestrator.getSessionForTask(req.params.id); + res.json({ + task, + session: session || null, + scoredResults: orchestrator.getScoredResultsForTask(req.params.id), + }); +}); + +// Get all tasks +router.get('/tasks', (_req: Request, res: Response) => { + const tasks = orchestrator.getAllTasks(); + res.json({ + count: tasks.length, + tasks, + }); +}); + +// Get agents for a task +router.get('/agents/:taskId', (req: Request, res: Response) => { + const agents = orchestrator.getAgentsForTask(req.params.taskId); + const scored = orchestrator.getScoredResultsForTask(req.params.taskId); + + res.json({ + taskId: req.params.taskId, + agentCount: agents.length, + agents, + scoredResults: scored, + }); +}); + +// Get reward ledger +router.get('/ledger', (_req: Request, res: Response) => { + const ledger = orchestrator.getLedger(); + const stats = x402Engine.getLedgerStats(); + + res.json({ + transactionCount: ledger.length, + transactions: ledger, + paymentStats: stats, + balances: x402Engine.getAllBalances(), + }); +}); + +// Get payment history for specific agent +router.get('/ledger/agent/:agentId', (req: Request, res: Response) => { + const ledger = orchestrator.getLedger(); + const agentTransactions = ledger.filter((t) => t.agentId === req.params.agentId); + const walletAddress = `agent-wallet-${req.params.agentId.slice(0, 12)}`; + + res.json({ + agentId: req.params.agentId, + transactionCount: agentTransactions.length, + transactions: agentTransactions, + balance: x402Engine.getBalance(walletAddress), + paymentHistory: x402Engine.getPaymentHistory(walletAddress), + }); +}); + +// Get system metrics +router.get('/metrics', (_req: Request, res: Response) => { + const metrics = orchestrator.getMetrics(); + const paymentStats = x402Engine.getLedgerStats(); + + res.json({ + swarm: metrics, + payments: paymentStats, + scoring: scoringEngine.getWeights(), + system: { + memoryUsage: process.memoryUsage(), + uptime: process.uptime(), + wsConnections: wsServer.getConnectionCount(), + }, + }); +}); + +// Get all swarm sessions +router.get('/sessions', (_req: Request, res: Response) => { + const sessions = orchestrator.getAllSessions(); + res.json({ + count: sessions.length, + sessions, + }); +}); + +// Get specific session +router.get('/session/:taskId', (req: Request, res: Response) => { + const session = orchestrator.getSessionForTask(req.params.taskId); + if (!session) { + res.status(404).json({ error: 'Session not found' }); + return; + } + res.json({ session }); +}); + +// Update scoring weights +router.put('/config/scoring', (req: Request, res: Response) => { + const { confidence, speed, reliability } = req.body; + try { + scoringEngine.updateWeights({ confidence, speed, reliability }); + res.json({ + success: true, + weights: scoringEngine.getWeights(), + }); + } catch (error) { + res.status(400).json({ + error: 'Invalid weights', + details: (error as Error).message, + }); + } +}); + +export { router as apiRouter }; \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/config/index.ts b/apps/fractal-inference-swarms/backend/src/config/index.ts new file mode 100644 index 0000000..3fb492c --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/config/index.ts @@ -0,0 +1,95 @@ +import dotenv from 'dotenv'; +import path from 'path'; + +dotenv.config({ path: path.resolve(__dirname, '../../.env') }); + +export interface AppConfig { + server: { + port: number; + host: string; + nodeEnv: string; + corsOrigin: string; + }; + websocket: { + port: number; + heartbeatInterval: number; + }; + orchestrator: { + maxConcurrentAgents: number; + defaultSwarmSize: number; + agentTimeoutMs: number; + taskSplitStrategy: 'semantic' | 'equal' | 'weighted'; + }; + scoring: { + weightConfidence: number; + weightSpeed: number; + weightReliability: number; + }; + reward: { + baseRewardTokens: number; + minScoreThreshold: number; + rewardPoolSize: number; + }; + logging: { + level: string; + format: string; + }; +} + +function parseFloat_(val: string | undefined, fallback: number): number { + if (!val) return fallback; + const parsed = parseFloat(val); + return isNaN(parsed) ? fallback : parsed; +} + +function parseInt_(val: string | undefined, fallback: number): number { + if (!val) return fallback; + const parsed = parseInt(val, 10); + return isNaN(parsed) ? fallback : parsed; +} + +export const config: AppConfig = { + server: { + port: parseInt_(process.env.PORT, 3001), + host: process.env.HOST || '0.0.0.0', + nodeEnv: process.env.NODE_ENV || 'development', + corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:3000', + }, + websocket: { + port: parseInt_(process.env.WS_PORT, 3002), + heartbeatInterval: parseInt_(process.env.WS_HEARTBEAT_INTERVAL, 30000), + }, + orchestrator: { + maxConcurrentAgents: parseInt_(process.env.MAX_CONCURRENT_AGENTS, 20), + defaultSwarmSize: parseInt_(process.env.DEFAULT_SWARM_SIZE, 5), + agentTimeoutMs: parseInt_(process.env.AGENT_TIMEOUT_MS, 30000), + taskSplitStrategy: (process.env.TASK_SPLIT_STRATEGY as AppConfig['orchestrator']['taskSplitStrategy']) || 'semantic', + }, + scoring: { + weightConfidence: parseFloat_(process.env.SCORING_WEIGHT_CONFIDENCE, 0.4), + weightSpeed: parseFloat_(process.env.SCORING_WEIGHT_SPEED, 0.3), + weightReliability: parseFloat_(process.env.SCORING_WEIGHT_RELIABILITY, 0.3), + }, + reward: { + baseRewardTokens: parseFloat_(process.env.BASE_REWARD_TOKENS, 100), + minScoreThreshold: parseFloat_(process.env.MIN_SCORE_THRESHOLD, 0.3), + rewardPoolSize: parseFloat_(process.env.REWARD_POOL_SIZE, 1000), + }, + logging: { + level: process.env.LOG_LEVEL || 'debug', + format: process.env.LOG_FORMAT || 'json', + }, +}; + +export function validateConfig(cfg: AppConfig): void { + const totalWeight = cfg.scoring.weightConfidence + cfg.scoring.weightSpeed + cfg.scoring.weightReliability; + if (Math.abs(totalWeight - 1.0) > 0.01) { + throw new Error(`Scoring weights must sum to 1.0, got ${totalWeight}`); + } + if (cfg.orchestrator.maxConcurrentAgents < 1) { + throw new Error('maxConcurrentAgents must be at least 1'); + } + if (cfg.reward.minScoreThreshold < 0 || cfg.reward.minScoreThreshold > 1) { + throw new Error('minScoreThreshold must be between 0 and 1'); + } +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/engine/MergeEngine.ts b/apps/fractal-inference-swarms/backend/src/engine/MergeEngine.ts new file mode 100644 index 0000000..68f05be --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/engine/MergeEngine.ts @@ -0,0 +1,210 @@ +import { ScoredResult } from '../models/AgentResult'; +import { MergedOutput, ValidationDetail } from '../models/Task'; +import { createChildLogger } from '../utils/logger'; + +const logger = createChildLogger('MergeEngine'); + +export type MergeStrategy = 'weighted_consensus' | 'best_pick' | 'hierarchical_merge'; + +export class MergeEngine { + mergeResults( + scoredResults: ScoredResult[], + taskId: string, + strategy: MergeStrategy = 'weighted_consensus' + ): MergedOutput { + logger.info('Merging agent outputs', { + taskId, + strategy, + resultCount: scoredResults.length, + }); + + let finalOutput: string; + + switch (strategy) { + case 'weighted_consensus': + finalOutput = this.weightedConsensusMerge(scoredResults); + break; + case 'best_pick': + finalOutput = this.bestPickMerge(scoredResults); + break; + case 'hierarchical_merge': + finalOutput = this.hierarchicalMerge(scoredResults); + break; + default: + finalOutput = this.weightedConsensusMerge(scoredResults); + } + + const validationDetails = this.validateMergedOutput(finalOutput, scoredResults); + const validationScore = validationDetails.reduce((sum, d) => sum + d.score, 0) / validationDetails.length; + const validationPassed = validationScore >= 0.6 && validationDetails.every((d) => d.score > 0.3); + + const merged: MergedOutput = { + taskId, + finalOutput, + contributingAgents: scoredResults.map((r) => r.agentId), + mergeStrategy: strategy, + validationScore: Math.round(validationScore * 10000) / 10000, + validationPassed, + validationDetails, + mergedAt: Date.now(), + }; + + logger.info('Merge complete', { + taskId, + validationScore: merged.validationScore, + validationPassed: merged.validationPassed, + contributingAgents: merged.contributingAgents.length, + }); + + return merged; + } + + private weightedConsensusMerge(results: ScoredResult[]): string { + const totalScore = results.reduce((sum, r) => sum + r.finalScore, 0); + const sections: string[] = []; + + sections.push('=== FRACTAL INFERENCE SWARM β€” MERGED OUTPUT ===\n'); + sections.push(`Strategy: Weighted Consensus | Agents: ${results.length} | Generated: ${new Date().toISOString()}\n`); + sections.push('--- SYNTHESIS ---\n'); + + const topResults = results + .filter((r) => r.finalScore >= totalScore / results.length * 0.5) + .sort((a, b) => b.finalScore - a.finalScore); + + if (topResults.length > 0) { + const primaryAgent = topResults[0]; + sections.push(`[Primary Output β€” Agent ${primaryAgent.agentId.slice(0, 8)}, Score: ${primaryAgent.finalScore.toFixed(4)}, Weight: ${(primaryAgent.finalScore / totalScore * 100).toFixed(1)}%]\n`); + sections.push(primaryAgent.output); + sections.push(''); + + if (topResults.length > 1) { + sections.push('\n--- SUPPLEMENTARY CONTRIBUTIONS ---\n'); + for (let i = 1; i < topResults.length; i++) { + const agent = topResults[i]; + const weight = (agent.finalScore / totalScore * 100).toFixed(1); + sections.push(`[Agent ${agent.agentId.slice(0, 8)}, Score: ${agent.finalScore.toFixed(4)}, Weight: ${weight}%]`); + sections.push(agent.output); + sections.push(''); + } + } + } + + sections.push('\n--- CONSENSUS METRICS ---'); + sections.push(`Total Contributing Agents: ${topResults.length}/${results.length}`); + sections.push(`Average Confidence: ${(results.reduce((s, r) => s + r.confidence, 0) / results.length).toFixed(4)}`); + sections.push(`Score Spread: ${(results[0].finalScore - results[results.length - 1].finalScore).toFixed(4)}`); + + return sections.join('\n'); + } + + private bestPickMerge(results: ScoredResult[]): string { + const best = results[0]; + const sections: string[] = []; + + sections.push('=== FRACTAL INFERENCE SWARM β€” MERGED OUTPUT ===\n'); + sections.push(`Strategy: Best Pick | Selected Agent: ${best.agentId.slice(0, 8)} | Score: ${best.finalScore.toFixed(4)}\n`); + sections.push('--- OUTPUT ---\n'); + sections.push(best.output); + sections.push('\n--- SELECTION RATIONALE ---'); + sections.push(`Selected agent ranked #1 out of ${results.length} agents`); + sections.push(`Confidence: ${best.confidence.toFixed(4)}`); + sections.push(`Reliability: ${best.reliabilityScore.toFixed(4)}`); + sections.push(`Compute Time: ${best.computeTimeMs}ms`); + + return sections.join('\n'); + } + + private hierarchicalMerge(results: ScoredResult[]): string { + const tiers = this.assignTiers(results); + const sections: string[] = []; + + sections.push('=== FRACTAL INFERENCE SWARM β€” MERGED OUTPUT ===\n'); + sections.push(`Strategy: Hierarchical Merge | Agents: ${results.length} | Tiers: ${Object.keys(tiers).length}\n`); + + for (const [tier, tierResults] of Object.entries(tiers)) { + sections.push(`\n--- TIER ${tier.toUpperCase()} ---\n`); + for (const result of tierResults) { + sections.push(`[Agent ${result.agentId.slice(0, 8)}, Score: ${result.finalScore.toFixed(4)}]`); + sections.push(result.output); + sections.push(''); + } + } + + sections.push('\n--- HIERARCHY SUMMARY ---'); + for (const [tier, tierResults] of Object.entries(tiers)) { + sections.push(`${tier}: ${tierResults.length} agents, avg score: ${(tierResults.reduce((s, r) => s + r.finalScore, 0) / tierResults.length).toFixed(4)}`); + } + + return sections.join('\n'); + } + + private assignTiers(results: ScoredResult[]): Record { + const tiers: Record = { primary: [], secondary: [], tertiary: [] }; + const maxScore = results[0]?.finalScore || 1; + + for (const result of results) { + const ratio = result.finalScore / maxScore; + if (ratio >= 0.8) { + tiers.primary.push(result); + } else if (ratio >= 0.5) { + tiers.secondary.push(result); + } else { + tiers.tertiary.push(result); + } + } + + return Object.fromEntries( + Object.entries(tiers).filter(([, v]) => v.length > 0) + ); + } + + private validateMergedOutput(output: string, results: ScoredResult[]): ValidationDetail[] { + const details: ValidationDetail[] = []; + + const hasContent = output.length > 100; + details.push({ + criterion: 'content_completeness', + passed: hasContent, + score: hasContent ? Math.min(1, output.length / 500) : 0.1, + message: hasContent ? 'Output contains substantial content' : 'Output is too short', + }); + + const avgConfidence = results.reduce((s, r) => s + r.confidence, 0) / results.length; + details.push({ + criterion: 'agent_confidence', + passed: avgConfidence >= 0.5, + score: avgConfidence, + message: `Average agent confidence: ${avgConfidence.toFixed(4)}`, + }); + + const topAgentWeight = results[0]?.finalScore / results.reduce((s, r) => s + r.finalScore, 0); + const diversityScore = 1 - (topAgentWeight || 1); + details.push({ + criterion: 'output_diversity', + passed: diversityScore >= 0.2, + score: Math.min(1, diversityScore * 2), + message: `Source diversity: ${(diversityScore * 100).toFixed(1)}% β€” ${diversityScore >= 0.2 ? 'multiple agents contributed' : 'dominated by single agent'}`, + }); + + const allConverged = results.every((r) => r.inferenceMetadata.convergenceReached); + details.push({ + criterion: 'convergence_check', + passed: allConverged, + score: allConverged ? 1 : results.filter((r) => r.inferenceMetadata.convergenceReached).length / results.length, + message: allConverged ? 'All agents reached convergence' : 'Some agents did not converge', + }); + + const scoreSpread = results[0]?.finalScore - results[results.length - 1]?.finalScore; + const consistencyScore = Math.max(0, 1 - scoreSpread * 2); + details.push({ + criterion: 'score_consistency', + passed: consistencyScore >= 0.4, + score: consistencyScore, + message: `Score spread: ${scoreSpread.toFixed(4)} β€” ${consistencyScore >= 0.4 ? 'consistent' : 'high variance'}`, + }); + + return details; + } +} + +export const mergeEngine = new MergeEngine(); \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/engine/RewardEngine.ts b/apps/fractal-inference-swarms/backend/src/engine/RewardEngine.ts new file mode 100644 index 0000000..ffd37bc --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/engine/RewardEngine.ts @@ -0,0 +1,108 @@ +import { v4 as uuidv4 } from 'uuid'; +import { ScoredResult, RewardTransaction } from '../models/AgentResult'; +import { config } from '../config'; +import { createChildLogger } from '../utils/logger'; + +const logger = createChildLogger('RewardEngine'); + +export class RewardEngine { + private baseReward: number; + private minScoreThreshold: number; + private rewardPool: number; + + constructor() { + this.baseReward = config.reward.baseRewardTokens; + this.minScoreThreshold = config.reward.minScoreThreshold; + this.rewardPool = config.reward.rewardPoolSize; + } + + distributeRewards(scoredResults: ScoredResult[], taskId: string): RewardTransaction[] { + if (scoredResults.length === 0) return []; + + logger.info('Distributing rewards', { + taskId, + agentCount: scoredResults.length, + pool: this.rewardPool, + }); + + const eligible = scoredResults.filter((r) => r.finalScore >= this.minScoreThreshold); + const ineligible = scoredResults.filter((r) => r.finalScore < this.minScoreThreshold); + + if (eligible.length === 0) { + logger.warn('No agents met minimum score threshold', { + taskId, + threshold: this.minScoreThreshold, + }); + return scoredResults.map((r) => this.createTransaction(r, taskId, 0, 'penalty')); + } + + const totalScore = eligible.reduce((sum, r) => sum + r.finalScore, 0); + const now = Date.now(); + + const transactions: RewardTransaction[] = []; + + for (const result of eligible) { + const normalizedScore = result.finalScore / totalScore; + let reward = this.baseReward * normalizedScore * eligible.length; + + if (result.rank === 1) { + reward *= 1.25; + } else if (result.rank === 2) { + reward *= 1.1; + } + + reward = Math.round(reward * 100) / 100; + + transactions.push(this.createTransaction(result, taskId, reward, 'inference_reward')); + } + + for (const result of ineligible) { + transactions.push(this.createTransaction(result, taskId, 0, 'penalty')); + } + + const totalDistributed = transactions.reduce((sum, t) => sum + t.tokensEarned, 0); + logger.info('Rewards distributed', { + taskId, + totalDistributed, + eligibleCount: eligible.length, + ineligibleCount: ineligible.length, + }); + + return transactions; + } + + private createTransaction( + result: ScoredResult, + taskId: string, + tokens: number, + type: RewardTransaction['transactionType'] + ): RewardTransaction { + return { + id: uuidv4(), + agentId: result.agentId, + taskId, + agentName: `Agent-${result.agentId.slice(0, 8)}`, + tokensEarned: tokens, + score: result.finalScore, + rank: result.rank, + transactionType: type, + timestamp: Date.now(), + x402PaymentId: uuidv4(), + metadata: { + confidence: result.confidence, + computeTimeMs: result.computeTimeMs, + reliability: result.reliabilityScore, + }, + }; + } + + getRewardConfig() { + return { + baseReward: this.baseReward, + minScoreThreshold: this.minScoreThreshold, + rewardPool: this.rewardPool, + }; + } +} + +export const rewardEngine = new RewardEngine(); \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/engine/ScoringEngine.ts b/apps/fractal-inference-swarms/backend/src/engine/ScoringEngine.ts new file mode 100644 index 0000000..403b1f6 --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/engine/ScoringEngine.ts @@ -0,0 +1,101 @@ +import { AgentResult, ScoredResult, ScoreBreakdown } from '../models/AgentResult'; +import { config } from '../config'; +import { createChildLogger } from '../utils/logger'; + +const logger = createChildLogger('ScoringEngine'); + +export interface ScoringWeights { + confidence: number; + speed: number; + reliability: number; +} + +export class ScoringEngine { + private weights: ScoringWeights; + + constructor(weights?: Partial) { + this.weights = { + confidence: weights?.confidence ?? config.scoring.weightConfidence, + speed: weights?.speed ?? config.scoring.weightSpeed, + reliability: weights?.reliability ?? config.scoring.weightReliability, + }; + + const total = this.weights.confidence + this.weights.speed + this.weights.reliability; + if (Math.abs(total - 1.0) > 0.01) { + logger.warn('Scoring weights do not sum to 1.0, normalizing', { total }); + this.weights.confidence /= total; + this.weights.speed /= total; + this.weights.reliability /= total; + } + } + + scoreResults(results: AgentResult[]): ScoredResult[] { + if (results.length === 0) return []; + + logger.info('Scoring agent results', { count: results.length }); + + const maxComputeTime = Math.max(...results.map((r) => r.computeTimeMs)); + const minComputeTime = Math.min(...results.map((r) => r.computeTimeMs)); + const computeRange = maxComputeTime - minComputeTime || 1; + + const scored: ScoredResult[] = results.map((result) => { + const rawConfidence = Math.max(0, Math.min(1, result.confidence)); + const rawSpeedScore = 1 - (result.computeTimeMs - minComputeTime) / computeRange; + const rawReliability = Math.max(0, Math.min(1, result.reliabilityScore)); + + const confidenceComponent = this.weights.confidence * rawConfidence; + const speedComponent = this.weights.speed * rawSpeedScore; + const reliabilityComponent = this.weights.reliability * rawReliability; + + const finalScore = confidenceComponent + speedComponent + reliabilityComponent; + + const breakdown: ScoreBreakdown = { + confidenceComponent, + speedComponent, + reliabilityComponent, + rawConfidence, + rawSpeedScore, + rawReliability, + }; + + return { + ...result, + finalScore: Math.round(finalScore * 10000) / 10000, + rank: 0, + scoreBreakdown: breakdown, + }; + }); + + scored.sort((a, b) => b.finalScore - a.finalScore); + scored.forEach((s, i) => { + s.rank = i + 1; + }); + + logger.info('Scoring complete', { + topScore: scored[0]?.finalScore, + bottomScore: scored[scored.length - 1]?.finalScore, + spread: scored[0]?.finalScore - scored[scored.length - 1]?.finalScore, + }); + + return scored; + } + + getWeights(): ScoringWeights { + return { ...this.weights }; + } + + updateWeights(newWeights: Partial): void { + if (newWeights.confidence !== undefined) this.weights.confidence = newWeights.confidence; + if (newWeights.speed !== undefined) this.weights.speed = newWeights.speed; + if (newWeights.reliability !== undefined) this.weights.reliability = newWeights.reliability; + + const total = this.weights.confidence + this.weights.speed + this.weights.reliability; + this.weights.confidence /= total; + this.weights.speed /= total; + this.weights.reliability /= total; + + logger.info('Scoring weights updated', this.weights); + } +} + +export const scoringEngine = new ScoringEngine(); \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/engine/TaskSplitter.ts b/apps/fractal-inference-swarms/backend/src/engine/TaskSplitter.ts new file mode 100644 index 0000000..f42e208 --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/engine/TaskSplitter.ts @@ -0,0 +1,220 @@ +import { v4 as uuidv4 } from 'uuid'; +import { SubTask } from '../models/Task'; +import { createChildLogger } from '../utils/logger'; +import { config } from '../config'; + +const logger = createChildLogger('TaskSplitter'); + +interface SplitConfig { + strategy: 'semantic' | 'equal' | 'weighted'; + swarmSize: number; + taskDescription: string; + taskId: string; +} + +const SEMANTIC_DECOMPOSITION_PATTERNS: Record = { + analysis: [ + 'Analyze the core requirements and identify key constraints', + 'Research existing approaches and best practices', + 'Evaluate potential solutions against defined criteria', + 'Synthesize findings into actionable recommendations', + 'Validate conclusions with cross-reference checks', + ], + development: [ + 'Design the architecture and define component interfaces', + 'Implement core logic and business rules', + 'Build integration layer and external connections', + 'Develop error handling and edge case coverage', + 'Create testing strategy and validate outputs', + ], + research: [ + 'Define research scope and identify key questions', + 'Gather primary sources and data points', + 'Analyze patterns and correlations in findings', + 'Cross-validate findings against secondary sources', + 'Compile findings into structured summary', + ], + creative: [ + 'Explore conceptual space and ideation', + 'Develop initial framework and structure', + 'Elaborate on core themes and details', + 'Refine and polish output quality', + 'Review for coherence and consistency', + ], + general: [ + 'Break down the problem into constituent parts', + 'Address the primary objective directly', + 'Handle secondary concerns and dependencies', + 'Validate and verify partial outputs', + 'Integrate and reconcile all partial results', + ], +}; + +function detectTaskCategory(description: string): string { + const lower = description.toLowerCase(); + const keywords: Record = { + analysis: ['analyze', 'evaluate', 'assess', 'review', 'examine', 'compare', 'benchmark'], + development: ['build', 'implement', 'create', 'develop', 'code', 'program', 'design system'], + research: ['research', 'investigate', 'study', 'explore', 'find out', 'discover', 'survey'], + creative: ['write', 'compose', 'generate', 'craft', 'design', 'imagine', 'story', 'content'], + }; + + let bestCategory = 'general'; + let bestScore = 0; + + for (const [category, words] of Object.entries(keywords)) { + const score = words.reduce((acc, word) => acc + (lower.includes(word) ? 1 : 0), 0); + if (score > bestScore) { + bestScore = score; + bestCategory = category; + } + } + + return bestCategory; +} + +function generateSubtaskDescription( + baseDescription: string, + pattern: string, + index: number, + total: number +): string { + return `[Subtask ${index + 1}/${total}] ${pattern}\n\nContext from parent task: ${baseDescription}`; +} + +function generateConstraints(index: number, total: number): string[] { + const constraints: string[] = [ + `Part ${index + 1} of ${total} β€” maintain consistency with other subtask outputs`, + 'Output must be self-contained yet compatible with merge operation', + 'Include confidence assessment in output', + ]; + + if (index === 0) { + constraints.push('Establish foundational context for subsequent subtasks'); + } + if (index === total - 1) { + constraints.push('Ensure coverage of any gaps from prior subtasks'); + } + + return constraints; +} + +export class TaskSplitter { + splitTask(cfg: SplitConfig): SubTask[] { + logger.info('Splitting task', { + taskId: cfg.taskId, + strategy: cfg.strategy, + swarmSize: cfg.swarmSize, + }); + + switch (cfg.strategy) { + case 'semantic': + return this.semanticSplit(cfg); + case 'equal': + return this.equalSplit(cfg); + case 'weighted': + return this.weightedSplit(cfg); + default: + return this.semanticSplit(cfg); + } + } + + private semanticSplit(cfg: SplitConfig): SubTask[] { + const category = detectTaskCategory(cfg.taskDescription); + const patterns = SEMANTIC_DECOMPOSITION_PATTERNS[category]; + const now = Date.now(); + + logger.debug('Detected task category', { category, taskId: cfg.taskId }); + + const subtasks: SubTask[] = []; + for (let i = 0; i < cfg.swarmSize; i++) { + const patternIndex = i % patterns.length; + subtasks.push({ + id: uuidv4(), + parentTaskId: cfg.taskId, + index: i, + description: generateSubtaskDescription( + cfg.taskDescription, + patterns[patternIndex], + i, + cfg.swarmSize + ), + context: `Category: ${category} | Strategy: semantic | Agent ${i + 1}/${cfg.swarmSize}`, + constraints: generateConstraints(i, cfg.swarmSize), + assignedAgentId: null, + status: 'pending', + createdAt: now, + completedAt: null, + }); + } + + logger.info('Task split complete', { + taskId: cfg.taskId, + subtaskCount: subtasks.length, + category, + }); + + return subtasks; + } + + private equalSplit(cfg: SplitConfig): SubTask[] { + const now = Date.now(); + const subtasks: SubTask[] = []; + + for (let i = 0; i < cfg.swarmSize; i++) { + subtasks.push({ + id: uuidv4(), + parentTaskId: cfg.taskId, + index: i, + description: `[Equal Split ${i + 1}/${cfg.swarmSize}] Process segment ${i + 1} of the task: ${cfg.taskDescription}`, + context: `Strategy: equal | Segment ${i + 1}/${cfg.swarmSize}`, + constraints: [ + `Handle exactly 1/${cfg.swarmSize} of the total workload`, + 'Maintain output format consistency', + 'Include segment boundaries in output', + ], + assignedAgentId: null, + status: 'pending', + createdAt: now, + completedAt: null, + }); + } + + return subtasks; + } + + private weightedSplit(cfg: SplitConfig): SubTask[] { + const now = Date.now(); + const weights = this.calculateWeights(cfg.swarmSize); + const subtasks: SubTask[] = []; + + for (let i = 0; i < cfg.swarmSize; i++) { + subtasks.push({ + id: uuidv4(), + parentTaskId: cfg.taskId, + index: i, + description: `[Weighted Split ${i + 1}/${cfg.swarmSize}, weight: ${weights[i].toFixed(2)}] ${cfg.taskDescription}`, + context: `Strategy: weighted | Weight: ${weights[i].toFixed(2)} | Priority rank: ${i + 1}`, + constraints: [ + `Allocated weight: ${(weights[i] * 100).toFixed(1)}% of total effort`, + 'Higher weight subtasks should produce more detailed output', + 'Include weight-proportional depth of analysis', + ], + assignedAgentId: null, + status: 'pending', + createdAt: now, + completedAt: null, + }); + } + + return subtasks; + } + + private calculateWeights(size: number): number[] { + const raw = Array.from({ length: size }, (_, i) => size - i); + const sum = raw.reduce((a, b) => a + b, 0); + return raw.map((w) => w / sum); + } +} + +export const taskSplitter = new TaskSplitter(); \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/middleware/errorHandler.ts b/apps/fractal-inference-swarms/backend/src/middleware/errorHandler.ts new file mode 100644 index 0000000..e09e77a --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/middleware/errorHandler.ts @@ -0,0 +1,36 @@ +import { Request, Response, NextFunction } from 'express'; +import { createChildLogger } from '../utils/logger'; + +const logger = createChildLogger('ErrorHandler'); + +export interface ApiError extends Error { + statusCode?: number; + details?: unknown; +} + +export function errorHandler(err: ApiError, _req: Request, res: Response, _next: NextFunction): void { + const statusCode = err.statusCode || 500; + const message = err.message || 'Internal server error'; + + logger.error('Unhandled error', { + statusCode, + message, + stack: err.stack, + details: err.details, + }); + + res.status(statusCode).json({ + error: message, + statusCode, + details: process.env.NODE_ENV === 'development' ? err.details || err.stack : undefined, + timestamp: Date.now(), + }); +} + +export function notFoundHandler(_req: Request, res: Response): void { + res.status(404).json({ + error: 'Endpoint not found', + statusCode: 404, + timestamp: Date.now(), + }); +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/middleware/requestLogger.ts b/apps/fractal-inference-swarms/backend/src/middleware/requestLogger.ts new file mode 100644 index 0000000..8b9bb2a --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/middleware/requestLogger.ts @@ -0,0 +1,28 @@ +import { Request, Response, NextFunction } from 'express'; +import { createChildLogger } from '../utils/logger'; + +const logger = createChildLogger('HTTP'); + +export function requestLogger(req: Request, res: Response, next: NextFunction): void { + const start = Date.now(); + + res.on('finish', () => { + const duration = Date.now() - start; + const logData = { + method: req.method, + path: req.path, + statusCode: res.statusCode, + durationMs: duration, + contentLength: res.get('content-length') || 0, + userAgent: req.get('user-agent')?.slice(0, 80), + }; + + if (res.statusCode >= 400) { + logger.warn('Request completed with error', logData); + } else { + logger.info('Request completed', logData); + } + }); + + next(); +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/models/AgentResult.ts b/apps/fractal-inference-swarms/backend/src/models/AgentResult.ts new file mode 100644 index 0000000..d706952 --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/models/AgentResult.ts @@ -0,0 +1,100 @@ +export type AgentStatus = + | 'idle' + | 'initializing' + | 'running' + | 'completed' + | 'failed' + | 'timeout'; + +export interface Agent { + id: string; + taskId: string; + subtaskId: string; + name: string; + status: AgentStatus; + spawnedAt: number; + startedAt: number | null; + completedAt: number | null; + computeTimeMs: number; + memoryUsageMb: number; + inferenceIterations: number; +} + +export interface AgentResult { + id: string; + agentId: string; + taskId: string; + subtaskId: string; + output: string; + confidence: number; + computeTimeMs: number; + reliabilityScore: number; + tokenCount: number; + inferenceMetadata: InferenceMetadata; + createdAt: number; +} + +export interface InferenceMetadata { + modelVersion: string; + temperature: number; + topP: number; + iterationsRun: number; + convergenceReached: boolean; + intermediateOutputs: string[]; +} + +export interface ScoredResult extends AgentResult { + finalScore: number; + rank: number; + scoreBreakdown: ScoreBreakdown; +} + +export interface ScoreBreakdown { + confidenceComponent: number; + speedComponent: number; + reliabilityComponent: number; + rawConfidence: number; + rawSpeedScore: number; + rawReliability: number; +} + +export interface RewardTransaction { + id: string; + agentId: string; + taskId: string; + agentName: string; + tokensEarned: number; + score: number; + rank: number; + transactionType: 'inference_reward' | 'bonus' | 'penalty'; + timestamp: number; + x402PaymentId: string; + metadata: Record; +} + +export interface SwarmSession { + id: string; + taskId: string; + agents: Agent[]; + results: AgentResult[]; + scoredResults: ScoredResult[]; + rewards: RewardTransaction[]; + status: 'active' | 'scoring' | 'merging' | 'completed' | 'failed'; + startedAt: number; + completedAt: number | null; + totalTokensDistributed: number; +} + +export interface SwarmMetrics { + totalSessions: number; + activeSessions: number; + totalAgentsSpawned: number; + totalTasksCompleted: number; + totalTokensDistributed: number; + averageSwarmSize: number; + averageCompletionTimeMs: number; + averageConfidence: number; + topAgentId: string | null; + topAgentScore: number; + uptimeMs: number; +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/models/Task.ts b/apps/fractal-inference-swarms/backend/src/models/Task.ts new file mode 100644 index 0000000..2ccd283 --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/models/Task.ts @@ -0,0 +1,89 @@ +export type TaskStatus = + | 'pending' + | 'splitting' + | 'spawning_agents' + | 'inference_running' + | 'scoring' + | 'merging' + | 'validating' + | 'distributing_rewards' + | 'completed' + | 'failed'; + +export type TaskPriority = 'low' | 'medium' | 'high' | 'critical'; + +export interface SubTask { + id: string; + parentTaskId: string; + index: number; + description: string; + context: string; + constraints: string[]; + assignedAgentId: string | null; + status: 'pending' | 'assigned' | 'running' | 'completed' | 'failed'; + createdAt: number; + completedAt: number | null; +} + +export interface Task { + id: string; + title: string; + description: string; + priority: TaskPriority; + status: TaskStatus; + subtasks: SubTask[]; + swarmSize: number; + metadata: Record; + mergedOutput: MergedOutput | null; + createdAt: number; + updatedAt: number; + completedAt: number | null; + totalComputeTimeMs: number; + errorMessage: string | null; +} + +export interface MergedOutput { + taskId: string; + finalOutput: string; + contributingAgents: string[]; + mergeStrategy: 'weighted_consensus' | 'best_pick' | 'hierarchical_merge'; + validationScore: number; + validationPassed: boolean; + validationDetails: ValidationDetail[]; + mergedAt: number; +} + +export interface ValidationDetail { + criterion: string; + passed: boolean; + score: number; + message: string; +} + +export interface TaskCreateRequest { + title: string; + description: string; + priority?: TaskPriority; + swarmSize?: number; + metadata?: Record; +} + +export function createTask(req: TaskCreateRequest, swarmSizeDefault: number): Task { + const { v4: uuidv4 } = require('uuid'); + return { + id: uuidv4(), + title: req.title, + description: req.description, + priority: req.priority || 'medium', + status: 'pending', + subtasks: [], + swarmSize: req.swarmSize || swarmSizeDefault, + metadata: req.metadata || {}, + mergedOutput: null, + createdAt: Date.now(), + updatedAt: Date.now(), + completedAt: null, + totalComputeTimeMs: 0, + errorMessage: null, + }; +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/payments/X402Mock.ts b/apps/fractal-inference-swarms/backend/src/payments/X402Mock.ts new file mode 100644 index 0000000..55c69ca --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/payments/X402Mock.ts @@ -0,0 +1,173 @@ +import { v4 as uuidv4 } from 'uuid'; +import { createChildLogger } from '../utils/logger'; + +const logger = createChildLogger('X402Mock'); + +export interface X402Payment { + paymentId: string; + from: string; + to: string; + amount: number; + currency: string; + status: 'pending' | 'processing' | 'completed' | 'failed'; + protocolVersion: string; + httpStatusCode: number; + headers: Record; + createdAt: number; + settledAt: number | null; +} + +export interface X402Ledger { + totalPayments: number; + totalVolume: number; + payments: X402Payment[]; + balances: Map; +} + +export class X402MockPaymentEngine { + private ledger: X402Ledger; + private processingDelayMs: number; + + constructor() { + this.ledger = { + totalPayments: 0, + totalVolume: 0, + payments: [], + balances: new Map(), + }; + this.processingDelayMs = 50; + } + + async processPayment( + fromAddress: string, + toAddress: string, + amount: number + ): Promise { + const payment: X402Payment = { + paymentId: uuidv4(), + from: fromAddress, + to: toAddress, + amount, + currency: 'x402-TOKEN', + status: 'pending', + protocolVersion: 'x402/1.0', + httpStatusCode: 402, + headers: { + 'X-402-Payment-Required': 'true', + 'X-402-Amount': amount.toString(), + 'X-402-Currency': 'x402-TOKEN', + 'X-402-Payment-Id': '', + 'X-402-Protocol-Version': 'x402/1.0', + }, + createdAt: Date.now(), + settledAt: null, + }; + + payment.headers['X-402-Payment-Id'] = payment.paymentId; + + logger.debug('Processing x402 payment', { + paymentId: payment.paymentId, + from: fromAddress, + to: toAddress, + amount, + }); + + payment.status = 'processing'; + + await this.simulateNetworkLatency(); + + const success = Math.random() > 0.02; + + if (success) { + payment.status = 'completed'; + payment.httpStatusCode = 200; + payment.settledAt = Date.now(); + + const currentBalance = this.ledger.balances.get(toAddress) || 0; + this.ledger.balances.set(toAddress, currentBalance + amount); + + this.ledger.totalVolume += amount; + + logger.info('x402 payment completed', { + paymentId: payment.paymentId, + amount, + newBalance: currentBalance + amount, + }); + } else { + payment.status = 'failed'; + payment.httpStatusCode = 500; + logger.error('x402 payment failed', { paymentId: payment.paymentId }); + } + + this.ledger.payments.push(payment); + this.ledger.totalPayments++; + + return payment; + } + + async processBatchPayments( + payments: Array<{ from: string; to: string; amount: number }> + ): Promise { + logger.info('Processing batch x402 payments', { count: payments.length }); + + const results = await Promise.all( + payments.map((p) => this.processPayment(p.from, p.to, p.amount)) + ); + + const successful = results.filter((r) => r.status === 'completed'); + const failed = results.filter((r) => r.status === 'failed'); + + logger.info('Batch payment complete', { + total: results.length, + successful: successful.length, + failed: failed.length, + totalVolume: successful.reduce((sum, p) => sum + p.amount, 0), + }); + + return results; + } + + getBalance(address: string): number { + return this.ledger.balances.get(address) || 0; + } + + getAllBalances(): Record { + return Object.fromEntries(this.ledger.balances); + } + + getPaymentHistory(address?: string): X402Payment[] { + if (address) { + return this.ledger.payments.filter( + (p) => p.from === address || p.to === address + ); + } + return [...this.ledger.payments]; + } + + getLedgerStats() { + return { + totalPayments: this.ledger.totalPayments, + totalVolume: Math.round(this.ledger.totalVolume * 100) / 100, + uniqueRecipients: this.ledger.balances.size, + averagePayment: + this.ledger.totalPayments > 0 + ? Math.round((this.ledger.totalVolume / this.ledger.totalPayments) * 100) / 100 + : 0, + successRate: + this.ledger.totalPayments > 0 + ? Math.round( + (this.ledger.payments.filter((p) => p.status === 'completed').length / + this.ledger.totalPayments) * + 10000 + ) / 100 + : 100, + }; + } + + private simulateNetworkLatency(): Promise { + const jitter = Math.random() * this.processingDelayMs; + return new Promise((resolve) => setTimeout(resolve, this.processingDelayMs + jitter)); + } +} + +export const x402Engine = new X402MockPaymentEngine(); \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/server.ts b/apps/fractal-inference-swarms/backend/src/server.ts new file mode 100644 index 0000000..fb92ee7 --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/server.ts @@ -0,0 +1,78 @@ +import express from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import http from 'http'; +import { config, validateConfig } from './config'; +import { apiRouter } from './api/routes'; +import { wsServer } from './websocket/socket'; +import { errorHandler, notFoundHandler } from './middleware/errorHandler'; +import { requestLogger } from './middleware/requestLogger'; +import { logger } from './utils/logger'; + +validateConfig(config); + +const app = express(); +const server = http.createServer(app); + +// Security & parsing +app.use(helmet({ contentSecurityPolicy: false })); +app.use(cors({ origin: config.server.corsOrigin, credentials: true })); +app.use(express.json({ limit: '10mb' })); +app.use(express.urlencoded({ extended: true })); + +// Logging +app.use(requestLogger); + +// API routes +app.use('/api', apiRouter); + +// Error handling +app.use(notFoundHandler); +app.use(errorHandler); + +// Initialize WebSocket +wsServer.initialize(server); + +// Start server +server.listen(config.server.port, '0.0.0.0', () => { + logger.info('=== Fractal Inference Swarms Backend ===', { + port: config.server.port, + host: '0.0.0.0', // Updated log to reflect reality + env: config.server.nodeEnv, + wsPath: '/ws', + corsOrigin: config.server.corsOrigin, + }); + logger.info('Orchestrator config', { + maxAgents: config.orchestrator.maxConcurrentAgents, + defaultSwarmSize: config.orchestrator.defaultSwarmSize, + splitStrategy: config.orchestrator.taskSplitStrategy, + }); + logger.info('Scoring weights', config.scoring); + logger.info('Reward config', config.reward); +}); + +// Graceful shutdown +function gracefulShutdown(signal: string): void { + logger.info(`${signal} received β€” shutting down gracefully`); + wsServer.shutdown(); + server.close(() => { + logger.info('Server closed'); + process.exit(0); + }); + setTimeout(() => { + logger.error('Forced shutdown after timeout'); + process.exit(1); + }, 10000); +} + +process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); +process.on('SIGINT', () => gracefulShutdown('SIGINT')); +process.on('unhandledRejection', (reason) => { + logger.error('Unhandled promise rejection', { reason }); +}); +process.on('uncaughtException', (error) => { + logger.error('Uncaught exception', { error: error.message, stack: error.stack }); + process.exit(1); +}); + +export { app, server }; \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/utils/EventBus.ts b/apps/fractal-inference-swarms/backend/src/utils/EventBus.ts new file mode 100644 index 0000000..19ee65d --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/utils/EventBus.ts @@ -0,0 +1,68 @@ +import { EventEmitter } from 'events'; + +export type SwarmEvent = + | 'task:created' + | 'task:splitting' + | 'task:subtasks_ready' + | 'task:agents_spawning' + | 'task:inference_started' + | 'task:inference_complete' + | 'task:scoring_started' + | 'task:scoring_complete' + | 'task:merging_started' + | 'task:merge_complete' + | 'task:validation_complete' + | 'task:rewards_distributed' + | 'task:completed' + | 'task:failed' + | 'agent:spawned' + | 'agent:started' + | 'agent:progress' + | 'agent:completed' + | 'agent:failed' + | 'reward:distributed' + | 'metrics:updated'; + +export interface SwarmEventPayload { + event: SwarmEvent; + taskId: string; + data: unknown; + timestamp: number; +} + +class EventBus extends EventEmitter { + private static instance: EventBus; + + private constructor() { + super(); + this.setMaxListeners(100); + } + + static getInstance(): EventBus { + if (!EventBus.instance) { + EventBus.instance = new EventBus(); + } + return EventBus.instance; + } + + emitSwarmEvent(event: SwarmEvent, taskId: string, data: unknown): void { + const payload: SwarmEventPayload = { + event, + taskId, + data, + timestamp: Date.now(), + }; + this.emit(event, payload); + this.emit('*', payload); + } + + onSwarmEvent(event: SwarmEvent | '*', handler: (payload: SwarmEventPayload) => void): void { + this.on(event, handler); + } + + offSwarmEvent(event: SwarmEvent | '*', handler: (payload: SwarmEventPayload) => void): void { + this.off(event, handler); + } +} + +export const eventBus = EventBus.getInstance(); \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/utils/logger.ts b/apps/fractal-inference-swarms/backend/src/utils/logger.ts new file mode 100644 index 0000000..b739a84 --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/utils/logger.ts @@ -0,0 +1,43 @@ +import winston from 'winston'; +import { config } from '../config'; + +const { combine, timestamp, printf, colorize, json } = winston.format; + +const consoleFormat = printf(({ level, message, timestamp: ts, ...meta }) => { + const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ''; + return `[${ts}] ${level}: ${message}${metaStr}`; +}); + +const transports: winston.transport[] = [ + new winston.transports.Console({ + format: config.logging.format === 'json' + ? combine(timestamp(), json()) + : combine(colorize(), timestamp({ format: 'HH:mm:ss.SSS' }), consoleFormat), + }), +]; + +if (config.server.nodeEnv === 'production') { + transports.push( + new winston.transports.File({ + filename: 'logs/error.log', + level: 'error', + maxsize: 10 * 1024 * 1024, + maxFiles: 5, + }), + new winston.transports.File({ + filename: 'logs/combined.log', + maxsize: 10 * 1024 * 1024, + maxFiles: 10, + }) + ); +} + +export const logger = winston.createLogger({ + level: config.logging.level, + defaultMeta: { service: 'fractal-inference-swarms' }, + transports, +}); + +export function createChildLogger(component: string): winston.Logger { + return logger.child({ component }); +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/src/websocket/socket.ts b/apps/fractal-inference-swarms/backend/src/websocket/socket.ts new file mode 100644 index 0000000..25f203b --- /dev/null +++ b/apps/fractal-inference-swarms/backend/src/websocket/socket.ts @@ -0,0 +1,200 @@ +import WebSocket, { WebSocketServer } from 'ws'; +import { Server as HttpServer } from 'http'; +import { eventBus, SwarmEventPayload } from '../utils/EventBus'; +import { config } from '../config'; +import { createChildLogger } from '../utils/logger'; + +const logger = createChildLogger('WebSocket'); + +interface TrackedClient { + ws: WebSocket; + id: string; + subscribedTasks: Set; + connectedAt: number; + lastPing: number; + isAlive: boolean; +} + +export class SwarmWebSocketServer { + private wss: WebSocketServer | null = null; + private clients: Map = new Map(); + private heartbeatInterval: NodeJS.Timeout | null = null; + private clientIdCounter: number = 0; + + initialize(server: HttpServer): void { + this.wss = new WebSocketServer({ server, path: '/ws' }); + + this.wss.on('connection', (ws: WebSocket) => { + const clientId = `client-${++this.clientIdCounter}-${Date.now()}`; + const client: TrackedClient = { + ws, + id: clientId, + subscribedTasks: new Set(), + connectedAt: Date.now(), + lastPing: Date.now(), + isAlive: true, + }; + + this.clients.set(clientId, client); + logger.info('Client connected', { clientId, totalClients: this.clients.size }); + + this.sendToClient(client, { + type: 'connection', + clientId, + timestamp: Date.now(), + message: 'Connected to Fractal Inference Swarms WebSocket', + }); + + ws.on('message', (data: Buffer) => { + try { + const message = JSON.parse(data.toString()); + this.handleClientMessage(client, message); + } catch { + this.sendToClient(client, { + type: 'error', + message: 'Invalid JSON message', + }); + } + }); + + ws.on('pong', () => { + client.isAlive = true; + client.lastPing = Date.now(); + }); + + ws.on('close', () => { + this.clients.delete(clientId); + logger.info('Client disconnected', { clientId, totalClients: this.clients.size }); + }); + + ws.on('error', (error) => { + logger.error('WebSocket client error', { clientId, error: error.message }); + this.clients.delete(clientId); + }); + }); + + this.setupEventForwarding(); + this.startHeartbeat(); + + logger.info('WebSocket server initialized'); + } + + private handleClientMessage(client: TrackedClient, message: Record): void { + switch (message.type) { + case 'subscribe': + if (typeof message.taskId === 'string') { + client.subscribedTasks.add(message.taskId); + this.sendToClient(client, { + type: 'subscribed', + taskId: message.taskId, + }); + logger.debug('Client subscribed to task', { + clientId: client.id, + taskId: message.taskId, + }); + } + break; + + case 'unsubscribe': + if (typeof message.taskId === 'string') { + client.subscribedTasks.delete(message.taskId); + this.sendToClient(client, { + type: 'unsubscribed', + taskId: message.taskId, + }); + } + break; + + case 'subscribe_all': + client.subscribedTasks.add('*'); + this.sendToClient(client, { type: 'subscribed', taskId: '*' }); + break; + + case 'ping': + this.sendToClient(client, { type: 'pong', timestamp: Date.now() }); + break; + + default: + this.sendToClient(client, { + type: 'error', + message: `Unknown message type: ${message.type}`, + }); + } + } + + private setupEventForwarding(): void { + eventBus.onSwarmEvent('*', (payload: SwarmEventPayload) => { + const message = { + type: 'swarm_event', + event: payload.event, + taskId: payload.taskId, + data: payload.data, + timestamp: payload.timestamp, + }; + + for (const client of this.clients.values()) { + if ( + client.subscribedTasks.has('*') || + client.subscribedTasks.has(payload.taskId) + ) { + this.sendToClient(client, message); + } + } + }); + } + + private sendToClient(client: TrackedClient, data: unknown): void { + if (client.ws.readyState === WebSocket.OPEN) { + try { + client.ws.send(JSON.stringify(data)); + } catch (error) { + logger.error('Failed to send to client', { + clientId: client.id, + error: (error as Error).message, + }); + } + } + } + + private startHeartbeat(): void { + this.heartbeatInterval = setInterval(() => { + for (const [id, client] of this.clients) { + if (!client.isAlive) { + logger.debug('Terminating unresponsive client', { clientId: id }); + client.ws.terminate(); + this.clients.delete(id); + continue; + } + client.isAlive = false; + client.ws.ping(); + } + }, config.websocket.heartbeatInterval); + } + + broadcast(data: unknown): void { + const message = JSON.stringify(data); + for (const client of this.clients.values()) { + if (client.ws.readyState === WebSocket.OPEN) { + client.ws.send(message); + } + } + } + + getConnectionCount(): number { + return this.clients.size; + } + + shutdown(): void { + if (this.heartbeatInterval) { + clearInterval(this.heartbeatInterval); + } + for (const client of this.clients.values()) { + client.ws.close(1001, 'Server shutting down'); + } + this.clients.clear(); + this.wss?.close(); + logger.info('WebSocket server shut down'); + } +} + +export const wsServer = new SwarmWebSocketServer(); \ No newline at end of file diff --git a/apps/fractal-inference-swarms/backend/tsconfig.json b/apps/fractal-inference-swarms/backend/tsconfig.json new file mode 100644 index 0000000..47e7468 --- /dev/null +++ b/apps/fractal-inference-swarms/backend/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "moduleResolution": "node", + "baseUrl": "./src", + "paths": { + "@/*": ["./*"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] + } \ No newline at end of file diff --git a/apps/fractal-inference-swarms/docker-compose.yml b/apps/fractal-inference-swarms/docker-compose.yml new file mode 100644 index 0000000..69a4d8b --- /dev/null +++ b/apps/fractal-inference-swarms/docker-compose.yml @@ -0,0 +1,49 @@ +version: '3.9' + +services: + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: fis-backend + ports: + - "3001:3001" + environment: + - NODE_ENV=production + - PORT=3001 + - HOST=0.0.0.0 + - CORS_ORIGIN=http://localhost:8080 + - WS_PORT=3001 + - WS_HEARTBEAT_INTERVAL=30000 + - MAX_CONCURRENT_AGENTS=20 + - DEFAULT_SWARM_SIZE=5 + - AGENT_TIMEOUT_MS=30000 + - TASK_SPLIT_STRATEGY=semantic + - SCORING_WEIGHT_CONFIDENCE=0.4 + - SCORING_WEIGHT_SPEED=0.3 + - SCORING_WEIGHT_RELIABILITY=0.3 + - BASE_REWARD_TOKENS=100 + - MIN_SCORE_THRESHOLD=0.3 + - REWARD_POOL_SIZE=1000 + - LOG_LEVEL=info + - LOG_FORMAT=json + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3001/api/health"] + interval: 30s + timeout: 10s + retries: 3 + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + container_name: fis-frontend + ports: + - "8080:8080" + depends_on: + backend: + condition: service_healthy + environment: + - NODE_ENV=production + restart: unless-stopped \ No newline at end of file diff --git a/apps/fractal-inference-swarms/frontend/.dockerignore b/apps/fractal-inference-swarms/frontend/.dockerignore new file mode 100644 index 0000000..d5292c4 --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/.dockerignore @@ -0,0 +1,6 @@ +node_modules +.next +.env +*.log +.git +.gitignore \ No newline at end of file diff --git a/apps/fractal-inference-swarms/frontend/.gitignore b/apps/fractal-inference-swarms/frontend/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/apps/fractal-inference-swarms/frontend/Dockerfile b/apps/fractal-inference-swarms/frontend/Dockerfile new file mode 100644 index 0000000..aa4839b --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/Dockerfile @@ -0,0 +1,40 @@ +FROM node:20-alpine AS deps + +WORKDIR /app + +COPY package.json package-lock.json* ./ +RUN npm ci + +FROM node:20-alpine AS builder + +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN npm run build + +FROM node:20-alpine AS runner + +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 swarm && \ + adduser --system --uid 1001 frontend + +COPY --from=builder /app/public ./public +COPY --from=builder --chown=frontend:swarm /app/.next/standalone ./ +COPY --from=builder --chown=frontend:swarm /app/.next/static ./.next/static + +USER frontend + +EXPOSE 8080 + +ENV PORT=8080 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] \ No newline at end of file diff --git a/apps/fractal-inference-swarms/frontend/README.md b/apps/fractal-inference-swarms/frontend/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/apps/fractal-inference-swarms/frontend/app/dashboard/page.tsx b/apps/fractal-inference-swarms/frontend/app/dashboard/page.tsx new file mode 100644 index 0000000..61656b3 --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/app/dashboard/page.tsx @@ -0,0 +1,241 @@ +'use client'; + +import React, { useState } from 'react'; +import { Activity, Database, Layers, Terminal, AlertCircle, Network, Home } from 'lucide-react'; +import Link from 'next/link'; +import MetricsPanel from '@/components/MetricsPanel'; +import TaskSubmitForm from '@/components/TaskSubmitForm'; +import TaskCard from '@/components/TaskCard'; +import AgentSwarmView from '@/components/AgentSwarmView'; +import Leaderboard from '@/components/Leaderboard'; +import LedgerHistory from '@/components/LedgerHistory'; +import EventFeed from '@/components/EventFeed'; +import { useWebSocket } from '@/hooks/useWebSocket'; +import { useSwarmData } from '@/hooks/useSwarmData'; + +export default function DashboardPage() { + const [selectedTaskId, setSelectedTaskId] = useState(null); + const { connected, events } = useWebSocket(() => { + swarmData.refresh(); + }); + const swarmData = useSwarmData(2500); + + return ( +
+ + {/* Animated Background */} +
+
+ + {/* Top Navigation Bar */} +
+
+
+ +
+ +
+

Fractal Console

+ +
+
+ ORD + MAINNET +
+
+ +
+
+
+ {connected ? 'SYSTEM ONLINE' : 'DISCONNECTED'} +
+ + + Home + +
+
+
+ +
+ + {/* Error State */} + {swarmData.error && ( +
+
+ + Connection Failure: {swarmData.error} +
+ +
+ )} + + {/* Metrics Panel - Full Width */} +
+ +
+ + {/* Main Dashboard Grid */} +
+ + {/* Left Column: Task Registry */} +
+
+
+
+
+
+ +
+
+

Task Queue

+

Active threads

+
+
+
+ {swarmData.tasks.length} +
+
+
+ +
+ {swarmData.loading && swarmData.tasks.length === 0 ? ( +
+
+
+ LOADING_REGISTRY... +
+
+ ) : swarmData.tasks.length === 0 ? ( +
+ + No active threads + Deploy a swarm to begin +
+ ) : ( +
+ {swarmData.tasks.map((task) => ( + + ))} +
+ )} +
+
+
+ + {/* Center Column: Main Visualizer */} +
+ {/* Main Swarm View */} +
+ {selectedTaskId ? ( + + ) : ( +
+
+ +

Awaiting Selection

+
+

SELECT_TASK_ID FROM REGISTRY

+
+
+ )} +
+ + {/* System Logs */} +
+
+
+ +
+
+

System Logs

+

Real-time event stream

+
+
+
+ +
+
+ + {/* Submit Task Form - Moved Below */} +
+ +
+
+ + {/* Right Column: Stats */} +
+ {/* Leaderboard */} +
+
+
+
+
+
+
+

Leaderboard

+

Top performers

+
+
+
+
+ +
+
+ + {/* x402 Ledger */} +
+
+
+
+
+
+
+

x402 Ledger

+

Transaction history

+
+
+
+
+ +
+
+
+ +
+
+ + {/* Custom Scrollbar Styles */} + +
+ ); +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/frontend/app/favicon.ico b/apps/fractal-inference-swarms/frontend/app/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/apps/fractal-inference-swarms/frontend/app/favicon.ico differ diff --git a/apps/fractal-inference-swarms/frontend/app/globals.css b/apps/fractal-inference-swarms/frontend/app/globals.css new file mode 100644 index 0000000..20d65be --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/app/globals.css @@ -0,0 +1,63 @@ +@import "tailwindcss"; + +@theme { + /* -- Core Palette -- */ + --color-background: #050505; /* Deep Void */ + --color-surface: #0f0f0f; /* Card Surface */ + --color-surface-hover: #1a1a1a; + --color-border: #222222; /* Subtle Border */ + + /* -- Brand Colors -- */ + --color-primary: #3b82f6; /* Electric Blue */ + --color-primary-dim: #1d4ed8; + --color-accent: #8b5cf6; /* AI Purple */ + --color-success: #10b981; /* Matrix Green */ + --color-warning: #f59e0b; /* Processing Orange */ + --color-error: #ef4444; /* System Red */ + + /* -- Text Colors -- */ + --color-text-main: #ededed; + --color-text-muted: #888888; + --color-text-dim: #444444; + + /* -- Fonts -- */ + --font-sans: var(--font-inter), ui-sans-serif, system-ui; + --font-mono: var(--font-jetbrains), ui-monospace, monospace; + + /* -- Animations -- */ + --animate-fade-in: fadeIn 0.8s ease-out forwards; + --animate-pulse-slow: pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite; + --animate-float: float 6s ease-in-out infinite; + + @keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } + } + @keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } + } +} + +/* -- Global Styles -- */ +body { + background-color: var(--color-background); + color: var(--color-text-main); + font-family: var(--font-sans); + overflow-x: hidden; +} + +/* Custom Scrollbar for that "Terminal" feel */ +::-webkit-scrollbar { + width: 8px; +} +::-webkit-scrollbar-track { + background: var(--color-background); +} +::-webkit-scrollbar-thumb { + background: var(--color-border); + border-radius: 4px; +} +::-webkit-scrollbar-thumb:hover { + background: var(--color-text-muted); +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/frontend/app/layout.tsx b/apps/fractal-inference-swarms/frontend/app/layout.tsx new file mode 100644 index 0000000..8acd327 --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/app/layout.tsx @@ -0,0 +1,23 @@ +import type { Metadata } from 'next'; +import './globals.css'; + +export const metadata: Metadata = { + title: 'Fractal Inference Swarms | AI Orchestration Dashboard', + description: 'AI-native orchestration system with fractal agent swarms and x402 micropayments', +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + + + {children} + + + ); +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/frontend/app/page.tsx b/apps/fractal-inference-swarms/frontend/app/page.tsx new file mode 100644 index 0000000..2347a96 --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/app/page.tsx @@ -0,0 +1,314 @@ +'use client'; + +import Link from 'next/link'; +import { ArrowRight, Cpu, Layers, Zap, ShieldCheck, Network, Terminal, Activity, Code, Globe } from 'lucide-react'; + +export default function LandingPage() { + const scrollToSection = (id: string) => { + const element = document.getElementById(id); + if (element) { + element.scrollIntoView({ behavior: 'smooth' }); + } + }; + + return ( +
+ + {/* Background Ambience */} +
+ + {/* Navbar */} + + + {/* Hero Section */} +
+
+ + + + + MAINNET BETA LIVE +
+ +

+ Orchestrate AI Swarms
at Planetary Scale +

+ +

+ Don't rely on a single model. Deploy fractal swarms that split tasks, + debate outcomes, and reach consensus. Powered by the x402 micropayment protocol. +

+ +
+ + Deploy Agent Swarm + + +
+ + {/* Hero Visual / Dashboard Preview */} +
+
+
+
+ {/* Fake UI Elements */} +
+
+
+ +
+ +
+ {[1,2,3].map(i => ( +
+ +
+ ))} +
+
+
+ ORCHESTRATOR: TASK_SPLIT_COMPLETE (3 SUBTASKS) +
+
+
+
+
+ + {/* Metrics Strip */} +
+
+ } /> + } /> + } /> + } /> +
+
+ + {/* Architecture Section */} +
+
+

Fractal Architecture

+

+ A modular system designed for reliability. The orchestrator manages lifecycle, + while swarms execute logic in parallel. +

+
+ +
+ } + title="Fractal Task Splitting" + desc="The Orchestrator recursively breaks high-level objectives into atomic subtasks distributed across N-agents. Complex problems become simple parallel executions." + /> + } + title="Consensus Scoring" + desc="We don't trust single outputs. Results are validated via a weighted scoring engine: (Confidence * Weight) + (Speed * Weight) + (Reliability * Weight)." + /> + } + title="x402 Micropayments" + desc="Automated settlement layer. Agents are rewarded instantly via mock-ledger tokens based on performance scores, incentivizing high-quality inference." + /> +
+
+ + {/* Protocol / Terminal Section */} +
+
+
+
+ + PROTOCOL LEVEL +
+

The x402 Payment Standard

+
+

+ Traditional AI APIs are black boxes. Fractal Swarms introduce Proof of Inference. + Agents must provide cryptographic proof of their work to release funds from the escrow smart contract. +

+
    +
  • +
    +
    +
    + Atomic Settlements per sub-task +
  • +
  • +
    +
    +
    + Reputation-weighted staking for agents +
  • +
  • +
    +
    +
    + Transparent ledger history (Immutable) +
  • +
+
+
+ + View Live Ledger + +
+
+ + {/* Code Block Visual */} +
+
+
+
+
+ x402-ledger.json +
+
+
+ 1 + transaction {`{`} +
+
+ 2 + "id": "tx_99a8b1c...", +
+
+ 3 + "agent_id": "swarm_alpha_01", +
+
+ 4 + "task_hash": "0x7f3...", +
+
+ 5 + "score": 0.985, +
+
+ 6 + "payout": {`{`} +
+
+ 7 + "amount": 450, +
+
+ 8 + "token": "X402" +
+
+ 9 + {`}`} +
+
+ 10 + {`}`} +
+
+
+
+
+ + {/* Network / Global Map Placeholder */} +
+
+ +

Global Node Distribution

+

+ Our swarms are running on decentralized edge compute across 12 regions. + Low latency. High availability. Zero downtime. +

+ + {/* Simple Grid Graphic */} +
+ {['USE-1', 'USW-2', 'EU-CENTRAL', 'ASIA-EAST', 'SA-EAST', 'AF-NORTH', 'OC-SOUTH', 'EU-WEST'].map((region) => ( +
+ {region} +
+
+ ))} +
+
+
+ + {/* Footer */} +
+
+
+
+
+ +
+ Fractal Swarm +
+

+ Next-generation AI orchestration powered by cryptographic proofs and micropayments. +

+
+
+

Platform

+ +
+
+

Legal

+ +
+
+
+ Β© 2026 Fractal Systems Inc. All rights reserved. +
+
+
+ ); +} + +function FeatureCard({ icon, title, desc }: { icon: React.ReactNode, title: string, desc: string }) { + return ( +
+
{icon}
+

{title}

+

{desc}

+
+ ); +} + +function MetricItem({ label, value, icon }: { label: string, value: string, icon: React.ReactNode }) { + return ( +
+
{icon}
+
+
{value}
+
{label}
+
+
+ ); +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/frontend/components/AgentSwarmView.tsx b/apps/fractal-inference-swarms/frontend/components/AgentSwarmView.tsx new file mode 100644 index 0000000..497e892 --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/components/AgentSwarmView.tsx @@ -0,0 +1,245 @@ +'use client'; + +import React, { useEffect, useState } from 'react'; +import { Task, ScoredResult, SwarmSession } from '@/types'; +import { getTask } from '@/lib/api'; +import StatusBadge from './StatusBadge'; + +interface AgentSwarmViewProps { + taskId: string; +} + +export default function AgentSwarmView({ taskId }: AgentSwarmViewProps) { + const [task, setTask] = useState(null); + const [session, setSession] = useState(null); + const [scored, setScored] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let mounted = true; + const load = async () => { + try { + const data = await getTask(taskId); + if (mounted) { + setTask(data.task); + setSession(data.session); + setScored(data.scoredResults); + setLoading(false); + } + } catch { + if (mounted) setLoading(false); + } + }; + load(); + const interval = setInterval(load, 2000); + return () => { + mounted = false; + clearInterval(interval); + }; + }, [taskId]); + + if (loading) { + return ( +
+
+
+
+
+ {[1, 2, 3].map((i) =>
)} +
+
+
+ ); + } + + if (!task) { + return ( +
+ Task not found +
+ ); + } + + return ( +
+ {/* Task Detail Header */} +
+
+
+

{task.title}

+

{task.description}

+
+ +
+
+ ID: {task.id.slice(0, 12)} + Agents: {task.swarmSize} + Subtasks: {task.subtasks.length} + {task.totalComputeTimeMs > 0 && Total: {(task.totalComputeTimeMs / 1000).toFixed(2)}s} +
+
+ + {/* Agent Grid */} + {session && session.agents.length > 0 && ( +
+

Swarm Agents

+
+ {session.agents.map((agent, i) => { + const agentScore = scored.find((s) => s.agentId === agent.id); + const reward = session.rewards.find((r) => r.agentId === agent.id); + + return ( +
+
+ {agent.name} + +
+
+
+ Compute + {agent.computeTimeMs > 0 ? `${(agent.computeTimeMs / 1000).toFixed(2)}s` : '...'} +
+
+ Iterations + {agent.inferenceIterations} +
+
+ Memory + {agent.memoryUsageMb > 0 ? `${agent.memoryUsageMb.toFixed(0)}MB` : '...'} +
+ {agentScore && ( + <> +
+
+ Score + {(agentScore.finalScore * 100).toFixed(2)}% +
+
+ Rank + + #{agentScore.rank} + +
+
+ Confidence + {(agentScore.confidence * 100).toFixed(1)}% +
+ + )} + {reward && ( +
+ Reward + {reward.tokensEarned.toFixed(2)} tokens +
+ )} +
+ {agentScore && ( +
+
+
+
+
+ )} +
+ ); + })} +
+
+ )} + + {/* Merged Output */} + {task.mergedOutput && ( +
+
+

Merged Output

+
+ + {task.mergedOutput.validationPassed ? 'VALIDATED' : 'VALIDATION ISSUES'} + + + {(task.mergedOutput.validationScore * 100).toFixed(1)}% + +
+
+ + {/* Validation Details */} +
+ {task.mergedOutput.validationDetails.map((detail, i) => ( +
+
{detail.criterion.replace(/_/g, ' ')}
+
+ {(detail.score * 100).toFixed(0)}% +
+
+ ))} +
+ +
+
+              {task.mergedOutput.finalOutput}
+            
+
+
+ Strategy: {task.mergedOutput.mergeStrategy.replace(/_/g, ' ')} + Contributing: {task.mergedOutput.contributingAgents.length} agents + Merged: {new Date(task.mergedOutput.mergedAt).toLocaleTimeString()} +
+
+ )} + + {/* Score Breakdown */} + {scored.length > 0 && ( +
+

Score Breakdown

+
+ + + + + + + + + + + + + {scored.map((s) => ( + + + + + + + + + ))} + +
RankAgentConfidenceSpeedReliabilityFinal Score
+ + #{s.rank} + + {s.agentId.slice(0, 10)} + {(s.scoreBreakdown.rawConfidence * 100).toFixed(1)}% + + {(s.scoreBreakdown.rawSpeedScore * 100).toFixed(1)}% + + {(s.scoreBreakdown.rawReliability * 100).toFixed(1)}% + + {(s.finalScore * 100).toFixed(2)}% +
+
+
+ )} +
+ ); +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/frontend/components/EventFeed.tsx b/apps/fractal-inference-swarms/frontend/components/EventFeed.tsx new file mode 100644 index 0000000..e712849 --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/components/EventFeed.tsx @@ -0,0 +1,80 @@ +'use client'; + +import React from 'react'; +import { SwarmEvent } from '@/types'; + +interface EventFeedProps { + events: SwarmEvent[]; +} + +const EVENT_ICONS: Record = { + 'task:created': '\u{1F4CB}', + 'task:splitting': '\u2702\uFE0F', + 'task:subtasks_ready': '\u{1F9E9}', + 'task:agents_spawning': '\u{1F680}', + 'task:inference_started': '\u{1F9E0}', + 'task:inference_complete': '\u2705', + 'task:scoring_started': '\u{1F4CA}', + 'task:scoring_complete': '\u{1F3AF}', + 'task:merging_started': '\u{1F500}', + 'task:merge_complete': '\u{1F4E6}', + 'task:validation_complete': '\u{1F6E1}\uFE0F', + 'task:rewards_distributed': '\u{1FA99}', + 'task:completed': '\u{1F389}', + 'task:failed': '\u274C', + 'agent:spawned': '\u{1F916}', + 'agent:started': '\u25B6\uFE0F', + 'agent:progress': '\u{1F504}', + 'agent:completed': '\u{1F7E2}', + 'agent:failed': '\u{1F534}', + 'reward:distributed': '\u{1F4B0}', + 'metrics:updated': '\u{1F4C8}', +}; + +const EVENT_COLORS: Record = { + 'task:created': 'text-blue-400', + 'task:completed': 'text-green-400', + 'task:failed': 'text-red-400', + 'agent:spawned': 'text-purple-400', + 'agent:completed': 'text-emerald-400', + 'agent:failed': 'text-red-400', + 'task:rewards_distributed': 'text-yellow-400', + 'task:merge_complete': 'text-indigo-400', + 'task:validation_complete': 'text-teal-400', +}; + +export default function EventFeed({ events }: EventFeedProps) { + const displayed = events.filter((e) => e.event !== 'agent:progress' && e.event !== 'metrics:updated').slice(0, 40); + + return ( +
+
+

Live Event Feed

+ {events.length} events +
+
+ {displayed.length === 0 ? ( +
Waiting for events...
+ ) : ( + displayed.map((event, i) => ( +
+ {EVENT_ICONS[event.event] || '\u26AA'} + + {new Date(event.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })} + + + {event.event} + + + {event.taskId.slice(0, 8)} + +
+ )) + )} +
+
+ ); +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/frontend/components/Leaderboard.tsx b/apps/fractal-inference-swarms/frontend/components/Leaderboard.tsx new file mode 100644 index 0000000..a0924ea --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/components/Leaderboard.tsx @@ -0,0 +1,98 @@ +'use client'; + +import React from 'react'; +import { RewardTransaction } from '@/types'; + +interface LeaderboardProps { + ledger: RewardTransaction[]; +} + +interface AgentAggregate { + agentId: string; + agentName: string; + totalTokens: number; + taskCount: number; + avgScore: number; + bestRank: number; +} + +export default function Leaderboard({ ledger }: LeaderboardProps) { + const aggregated = new Map(); + + for (const tx of ledger) { + const existing = aggregated.get(tx.agentId); + if (existing) { + existing.totalTokens += tx.tokensEarned; + existing.taskCount++; + existing.avgScore = (existing.avgScore * (existing.taskCount - 1) + tx.score) / existing.taskCount; + existing.bestRank = Math.min(existing.bestRank, tx.rank); + } else { + aggregated.set(tx.agentId, { + agentId: tx.agentId, + agentName: tx.agentName, + totalTokens: tx.tokensEarned, + taskCount: 1, + avgScore: tx.score, + bestRank: tx.rank, + }); + } + } + + const sorted = Array.from(aggregated.values()) + .filter((a) => a.totalTokens > 0) + .sort((a, b) => b.totalTokens - a.totalTokens) + .slice(0, 15); + + const maxTokens = sorted[0]?.totalTokens || 1; + + if (sorted.length === 0) { + return ( +
+

Agent Leaderboard

+
No agent data yet. Submit a task to populate the leaderboard.
+
+ ); + } + + return ( +
+

Agent Leaderboard

+
+ {sorted.map((agent, i) => { + const barWidth = (agent.totalTokens / maxTokens) * 100; + const medal = i === 0 ? '\u{1F947}' : i === 1 ? '\u{1F948}' : i === 2 ? '\u{1F949}' : ''; + + return ( +
+
+
+
+ + {medal || #{i + 1}} + +
+
{agent.agentName}
+
+ {agent.taskCount} tasks | avg score: {(agent.avgScore * 100).toFixed(1)}% | best: #{agent.bestRank} +
+
+
+
+
{agent.totalTokens.toFixed(2)}
+
tokens
+
+
+
+ ); + })} +
+
+ ); +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/frontend/components/LedgerHistory.tsx b/apps/fractal-inference-swarms/frontend/components/LedgerHistory.tsx new file mode 100644 index 0000000..62fe9df --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/components/LedgerHistory.tsx @@ -0,0 +1,80 @@ +'use client'; + +import React from 'react'; +import { RewardTransaction } from '@/types'; + +interface LedgerHistoryProps { + transactions: RewardTransaction[]; +} + +export default function LedgerHistory({ transactions }: LedgerHistoryProps) { + const recent = transactions.slice(0, 30); + + if (recent.length === 0) { + return ( +
+

x402 Payment Ledger

+
No transactions recorded yet.
+
+ ); + } + + return ( +
+
+

x402 Payment Ledger

+ {transactions.length} total transactions +
+
+ + + + + + + + + + + + + + {recent.map((tx) => ( + + + + + + + + + + ))} + +
TimeAgentTypeScoreRankTokensPayment ID
+ {new Date(tx.timestamp).toLocaleTimeString()} + {tx.agentName} + + {tx.transactionType.replace(/_/g, ' ')} + + + {(tx.score * 100).toFixed(1)}% + + #{tx.rank} + 0 ? 'text-swarm-success' : 'text-swarm-danger'}`}> + {tx.tokensEarned > 0 ? '+' : ''}{tx.tokensEarned.toFixed(2)} + + {tx.x402PaymentId.slice(0, 12)}... +
+
+
+ ); +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/frontend/components/MetricsPanel.tsx b/apps/fractal-inference-swarms/frontend/components/MetricsPanel.tsx new file mode 100644 index 0000000..e6ce128 --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/components/MetricsPanel.tsx @@ -0,0 +1,94 @@ +'use client'; + +import React from 'react'; +import { SwarmMetrics } from '@/types'; + +interface MetricCardProps { + label: string; + value: string | number; + subtext?: string; + color: string; + icon: string; +} + +function MetricCard({ label, value, subtext, color, icon }: MetricCardProps) { + return ( +
+
+ {label} + {icon} +
+
{value}
+ {subtext &&
{subtext}
} +
+ ); +} + +interface MetricsPanelProps { + metrics: SwarmMetrics | null; + connected: boolean; +} + +export default function MetricsPanel({ metrics, connected }: MetricsPanelProps) { + if (!metrics) { + return ( +
+ {Array.from({ length: 6 }).map((_, i) => ( +
+
+
+
+ ))} +
+ ); + } + + const uptimeHours = (metrics.uptimeMs / 3600000).toFixed(1); + + return ( +
+ + + 0 ? (metrics.averageCompletionTimeMs / 1000).toFixed(1) + 's avg' : 'n/a'}`} + color="text-swarm-success" + icon="\u2705" + /> + + + +
+ ); +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/frontend/components/StatusBadge.tsx b/apps/fractal-inference-swarms/frontend/components/StatusBadge.tsx new file mode 100644 index 0000000..c01a490 --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/components/StatusBadge.tsx @@ -0,0 +1,38 @@ +'use client'; + +import React from 'react'; + +const STATUS_COLORS: Record = { + pending: 'bg-gray-600 text-gray-200', + splitting: 'bg-blue-600/30 text-blue-300 border border-blue-500/50', + spawning_agents: 'bg-purple-600/30 text-purple-300 border border-purple-500/50 animate-pulse', + inference_running: 'bg-cyan-600/30 text-cyan-300 border border-cyan-500/50 animate-pulse', + scoring: 'bg-amber-600/30 text-amber-300 border border-amber-500/50', + merging: 'bg-indigo-600/30 text-indigo-300 border border-indigo-500/50 animate-pulse', + validating: 'bg-teal-600/30 text-teal-300 border border-teal-500/50', + distributing_rewards: 'bg-emerald-600/30 text-emerald-300 border border-emerald-500/50', + completed: 'bg-green-600/30 text-green-300 border border-green-500/50', + failed: 'bg-red-600/30 text-red-300 border border-red-500/50', + active: 'bg-cyan-600/30 text-cyan-300 border border-cyan-500/50 animate-pulse', + idle: 'bg-gray-600/30 text-gray-300', + initializing: 'bg-blue-600/30 text-blue-300 animate-pulse', + running: 'bg-cyan-600/30 text-cyan-300 animate-pulse', + timeout: 'bg-orange-600/30 text-orange-300', +}; + +interface StatusBadgeProps { + status: string; + size?: 'sm' | 'md'; +} + +export default function StatusBadge({ status, size = 'sm' }: StatusBadgeProps) { + const colorClass = STATUS_COLORS[status] || 'bg-gray-600 text-gray-200'; + const sizeClass = size === 'sm' ? 'text-xs px-2 py-0.5' : 'text-sm px-3 py-1'; + + return ( + + + {status.replace(/_/g, ' ')} + + ); +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/frontend/components/TaskCard.tsx b/apps/fractal-inference-swarms/frontend/components/TaskCard.tsx new file mode 100644 index 0000000..25dcb74 --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/components/TaskCard.tsx @@ -0,0 +1,97 @@ +'use client'; + +import React, { useState } from 'react'; +import { Task } from '@/types'; +import StatusBadge from './StatusBadge'; + +interface TaskCardProps { + task: Task; + onSelect: (taskId: string) => void; + isSelected: boolean; +} + +const PIPELINE_STAGES: Array<{ key: string; label: string }> = [ + { key: 'pending', label: 'Queued' }, + { key: 'splitting', label: 'Split' }, + { key: 'spawning_agents', label: 'Spawn' }, + { key: 'inference_running', label: 'Infer' }, + { key: 'scoring', label: 'Score' }, + { key: 'merging', label: 'Merge' }, + { key: 'distributing_rewards', label: 'Reward' }, + { key: 'completed', label: 'Done' }, +]; + +function getStageIndex(status: string): number { + const idx = PIPELINE_STAGES.findIndex((s) => s.key === status); + return idx >= 0 ? idx : 0; +} + +export default function TaskCard({ task, onSelect, isSelected }: TaskCardProps) { + const stageIdx = getStageIndex(task.status); + const elapsed = task.completedAt + ? ((task.completedAt - task.createdAt) / 1000).toFixed(1) + : ((Date.now() - task.createdAt) / 1000).toFixed(0); + + return ( +
onSelect(task.id)} + className={`bg-swarm-card border rounded-xl p-4 cursor-pointer transition-all duration-300 animate-fade-in ${ + isSelected + ? 'border-swarm-accent/60 ring-1 ring-swarm-accent/20 shadow-lg shadow-blue-900/20' + : 'border-swarm-border hover:border-swarm-accent/30' + }`} + > +
+
+

{task.title}

+

{task.description}

+
+ +
+ + {/* Pipeline progress bar */} +
+ {PIPELINE_STAGES.map((stage, i) => { + const isActive = i === stageIdx && task.status !== 'completed' && task.status !== 'failed'; + const isComplete = i < stageIdx || task.status === 'completed'; + const isFailed = task.status === 'failed'; + + return ( +
+
+ + {stage.label} + +
+ ); + })} +
+ +
+
+ {task.swarmSize} agents + {task.subtasks.length} subtasks +
+ {elapsed}s +
+ + {task.mergedOutput && ( +
+ {task.mergedOutput.validationPassed ? '\u2713 Validated' : '\u26A0 Validation issues'} + score: {(task.mergedOutput.validationScore * 100).toFixed(1)}% + strategy: {task.mergedOutput.mergeStrategy.replace(/_/g, ' ')} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/apps/fractal-inference-swarms/frontend/components/TaskSubmitForm.tsx b/apps/fractal-inference-swarms/frontend/components/TaskSubmitForm.tsx new file mode 100644 index 0000000..263d9bb --- /dev/null +++ b/apps/fractal-inference-swarms/frontend/components/TaskSubmitForm.tsx @@ -0,0 +1,162 @@ +'use client'; + +import React, { useState } from 'react'; +import { submitTask } from '@/lib/api'; + +interface TaskSubmitFormProps { + onSubmitted: () => void; +} + +const PRESET_TASKS = [ + { + title: 'Market Analysis: AI Infrastructure', + description: 'Analyze the current state of AI infrastructure market including cloud providers, edge computing, and specialized hardware. Evaluate key players, market trends, pricing models, and predict growth trajectories for the next 5 years.', + priority: 'high' as const, + swarmSize: 5, + }, + { + title: 'Security Audit: Microservices Architecture', + description: 'Conduct a comprehensive security audit of a microservices architecture. Evaluate authentication flows, inter-service communication, data encryption, API gateway vulnerabilities, and container orchestration security posture.', + priority: 'critical' as const, + swarmSize: 6, + }, + { + title: 'Research: Quantum-Resistant Cryptography', + description: 'Research the latest developments in post-quantum cryptographic algorithms. Compare lattice-based, hash-based, and code-based approaches. Evaluate NIST standardization candidates and implementation readiness for production systems.', + priority: 'medium' as const, + swarmSize: 4, + }, + { + title: 'Design System: Component Architecture', + description: 'Design a scalable component architecture for a design system supporting web and mobile platforms. Define token systems, composability patterns, accessibility requirements, and theming capabilities with performance constraints.', + priority: 'medium' as const, + swarmSize: 5, + }, +]; + +export default function TaskSubmitForm({ onSubmitted }: TaskSubmitFormProps) { + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [priority, setPriority] = useState('medium'); + const [swarmSize, setSwarmSize] = useState(5); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + const [showPresets, setShowPresets] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + if (!title.trim() || !description.trim()) return; + + setSubmitting(true); + setError(null); + + try { + await submitTask({ title: title.trim(), description: description.trim(), priority, swarmSize }); + setTitle(''); + setDescription(''); + setPriority('medium'); + setSwarmSize(5); + onSubmitted(); + } catch (err) { + setError((err as Error).message); + } finally { + setSubmitting(false); + } + } + + function loadPreset(preset: typeof PRESET_TASKS[number]) { + setTitle(preset.title); + setDescription(preset.description); + setPriority(preset.priority); + setSwarmSize(preset.swarmSize); + setShowPresets(false); + } + + return ( +
+
+

Submit Task to Swarm

+ +
+ + {showPresets && ( +
+ {PRESET_TASKS.map((preset, i) => ( + + ))} +
+ )} + +
+ setTitle(e.target.value)} + placeholder="Task title..." + className="w-full px-4 py-2.5 bg-swarm-surface border border-swarm-border rounded-lg text-swarm-text placeholder-swarm-muted focus:outline-none focus:border-swarm-accent/60 focus:ring-1 focus:ring-swarm-accent/30 transition-all text-sm" + required + /> +