Spaces:
Sleeping
feat(refactor): ready for manual QA after main sync (#274)
Browse files* fix: preserve gateway token query in websocket URLs
* fix: classify secure-context device identity handshake errors
* fix: normalize trailing dot in host allowlist checks
* fix: support proxied gateway websocket paths and tailnet host normalization
* fix: auto-connect startup to primary configured gateway
* fix: keep gateway tokens server-side only
* fix: allow authenticated viewers to resolve gateway connect credentials
* fix: identify mission control as operator gateway client
* fix: redirect remote http sessions to https for gateway auth
* fix: support URL-style gateway hosts in health probes
* fix: resolve primary gateway credentials from detected openclaw runtime
* fix: hide duplicate gateway connection summary when managed
* refactor: remove super admin and workspaces panels from UI navigation
* fix: treat configured gateways as full-mode capability
* refactor: move promo banner copy into subtle footer note
* fix: stabilize gateway websocket connect protocol detection
* test: cover https forwarded proto for gateway websocket url
* fix: load canonical agent files and memory in detail panel
* fix: resolve agent files from openclaw workspace conventions
* fix: persist websocket client across route remounts
* feat: allow deleting agents with optional workspace removal
* feat: refresh mission control branding and favicon assets
* feat: complete github parity sync implementation
* chore: remove e2e temp artifacts from repo
* feat: add embedded /chat panel with shared chat workspace
* feat: unify sessions navigation into chat panel
* feat: show local Claude/Codex sessions in chat list
* feat: enable local session continuation and chat tagging
* fix: correct local codex session recency detection
* fix: refresh local session age and anchor chat composer
* refactor: make chat provider-session-first by mode
* fix: add local provider and MC health rows in overview
* feat: finalize tenant-scoped workspaces and full e2e coverage
* feat: improve session workbench controls and smoke coverage
* refactor: extract SaaS code to separate pro repo
- Add registerAuthResolver() hook to auth.ts
- Add registerMigrations() hook to migrations.ts
- Remove saas config block, SaaS modules, Pro API routes
- Keep adapters, super-admin routes, migration 032
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add framework adapters and self-update mechanism
- Framework adapter layer for multi-agent registration (autogen, crewai, langgraph, claude-sdk, openclaw, generic)
- Self-update API endpoint (admin-only git pull + install + build)
- Update banner UI component showing available versions with dismiss
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update README stats, remove stale Super Admin refs, improve self-update
- Panel count 28→32, API routes 66→95, migrations 21→30
- Remove Super Admin from UI-facing docs (APIs remain)
- Document framework adapters and self-update mechanism
- Mark workspace isolation, adapters, projects as completed in roadmap
- Self-update now uses tag-based checkout instead of branch pull
- Plugin hook comments: "Pro" → "extensions"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add skills hub with registry integration, bidirectional sync, and local agent discovery
- Bidirectional disk↔DB skill sync via scheduler (60s interval, SHA-256 change detection, disk-wins conflict resolution)
- ClawdHub + skills.sh registry proxy with search, install, and security scanning (9 rules: prompt injection, credential leaks, data exfiltration, obfuscated content)
- Local agent discovery from ~/.agents/, ~/.codex/agents/, ~/.claude/agents/ with bidirectional sync
- DB-backed skills API with filesystem fallback, admin-only install, rate limiting
- Skills panel: installed/registry tabs, security badges, friendly source labels, OpenClaw gateway support
- Agent panel: local sync button, source badges (local/gateway)
- Migrations 033 (skills table) and 034 (agents source/hash/workspace columns)
- Full test coverage: 24 unit tests, 34 E2E tests (286 total suite green)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add per-agent rate limiting and agent self-registration
- Per-agent rate limiter keyed on x-agent-name header (falls back to IP)
- Agent heartbeat: 30 req/min per agent, task polling: 20 req/min per agent
- Rate limit response headers (Retry-After, X-RateLimit-*) for agent backoff
- POST /api/agents/register: self-service registration with viewer-level auth
- Idempotent registration (re-registering updates last_seen, returns existing)
- Name validation, role whitelist, capabilities/framework in config
- Self-registration rate-limited to 5/min per IP
- 9 E2E tests for self-registration (295 total suite green)
- README: updated API route count (97), test counts, new endpoints
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: enhance agent cost panel, OAuth approval UI, and framework adapter gateway
- Agent Cost Panel: add task cost attribution drill-down, cost share
percentages, bar chart comparison, 5th summary card for task-attributed
costs, 30s auto-refresh, and tabbed expanded view (tasks/models)
- OAuth Approval UI: replace window.prompt() with inline role selector
and note input, add avatar display, show animated pending count badge,
add dedicated "awaiting approval" state on login page
- Framework Adapter Gateway: wire GenericAdapter.getAssignments() to
query task queue, add POST /api/adapters route for framework-agnostic
agent actions (register, heartbeat, report, assignments, disconnect)
- Clean up dead api-keys import and DB-backed key resolution from
auth.ts (moved to Pro repo)
- Resolve README merge conflicts, update route count to 98
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: complete free-tier functionality — adapters, workspace CRUD, agent sync, and UI polish
- Implement all 5 framework adapter stubs (claude-sdk, crewai, langgraph, autogen, openclaw)
with shared queryPendingAssignments() helper to eliminate SQL duplication
- Add recurring gateway_agent_sync scheduler task (was startup-only)
- Add workspace CRUD API: POST/PUT/DELETE /api/workspaces with tenant isolation
- Add local agent discovery for flat .md files (Claude Code agent format with YAML frontmatter)
- Add per-agent cost breakdown API (GET /api/tokens/by-agent)
- Add API key rotation endpoint (GET/POST /api/tokens/rotate)
- Add Google OAuth disconnect endpoint
- Polish login page with inline Google Sign-In button
- Enhance settings panel with Security tab (API key management, OAuth)
- Enhance agent cost panel with per-agent DB view
- Add Awesome OpenClaw as third skill registry source
- Add integration connectivity test fallback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: include session-message component (required by chat-workspace)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: gateway dot color should reflect live connection state
When WebSocket is connected, show green dot regardless of stored
probe status. Prevents misleading red dot + green CONNECTED badge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: agent creation progress modal and openclaw CLI flag
- Remove invalid --name flag from openclaw agents add CLI invocation
- Add multi-step progress UI to CreateAgentModal showing DB insert,
gateway write, and workspace provisioning steps with animated status
- Progress view replaces review content during creation with spinner,
checkmark, and error states per step
- Auto-close on success after 1.5s, retry/close buttons on error
- Squad panel: add status-based card edge colors and glow styles
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: task dispatch — scheduler polls assigned tasks and runs agents via openclaw CLI
Adds a task_dispatch scheduler job that picks up tasks in 'assigned' status,
executes them via `openclaw agent --local --json`, and moves them to 'review'
with the agent's response as resolution + comment.
* feat: link dispatched tasks to agent sessions — view session from task detail
- task-dispatch: extract sessionId from openclaw JSON response, store in task metadata
- task detail modal: "View Session" button navigates to /chat with the agent's session transcript
- shows pulsing "Live" indicator when task is in_progress
- agent squad panel: show quality_review and done counts in task stats
* feat: automated Aegis quality review — scheduler polls review tasks and approves/rejects
- aegis_review scheduler job picks up tasks in 'review' status every 60s
- runs openclaw agent to evaluate task resolution quality
- approved → done, rejected → in_progress with feedback as comment
- quality-review API: rejected reviews now push task back to in_progress
- approved reviews work for any reviewer (not just aegis)
* feat: natural language recurring tasks + Claude Code task bridge
Add NL schedule parser (zero deps) for creating recurring tasks via
"every morning at 9am" style input. Template-clone pattern spawns dated
child tasks on cron schedule with Aegis quality gates per instance.
Read-only bridge surfaces Claude Code team tasks and configs from
~/.claude/tasks/ and ~/.claude/teams/ on the MC dashboard.
New files: schedule-parser.ts, recurring-tasks.ts, claude-tasks.ts,
API routes for /claude-tasks and /schedule-parse.
Modified: scheduler.ts (recurring_task_spawn), migrations.ts (036),
task-board-panel.tsx (schedule UI + badges + CC section),
cron-management-panel.tsx (CC teams section).
* docs: update README with recurring tasks and Claude Code task bridge
Add sections for natural language recurring tasks, Claude Code task
bridge, new API endpoints, architecture tree entries, and roadmap items.
* fix: agent card redesign, gateway badge tooltip, and ws:// for localhost gateways
- Compact agent cards: remove 4 colored stat boxes, show inline task stats,
display model name from config, remove session info box, remove Busy button
- Gateway ConnectionBadge: rich hover tooltip with host, latency, WS/SSE status
- Fix gateway connect over T
- .env.example +2 -1
- .github/workflows/quality-gate.yml +1 -1
- .gitignore +6 -0
- .node-version +1 -0
- .nvmrc +1 -0
- Dockerfile +11 -4
- README.md +177 -27
- SECURITY.md +38 -1
- SKILL.md +278 -0
- docker-compose.hardened.yml +21 -0
- docker-compose.yml +22 -0
- docs/LANDING-PAGE-HANDOFF.md +248 -0
- docs/SECURITY-HARDENING.md +277 -0
- docs/deployment.md +26 -0
- docs/plans/2026-03-10-onboarding-walkthrough-hardening.md +31 -0
- install.sh +429 -0
- next.config.js +6 -2
- openclaw_hardening_guide.md +124 -0
- package.json +17 -11
- playwright.config.ts +2 -4
- pnpm-lock.yaml +781 -0
- public/brand/claude-logo.png +3 -0
- public/brand/codex-logo.png +3 -0
- public/brand/hermes-logo.png +3 -0
- public/brand/mc-logo-128.png +3 -0
- public/brand/mc-logo-256.png +3 -0
- public/brand/mc-logo-512.png +3 -0
- public/brand/openclaw-logo.png +3 -0
- public/mc-logo.png +3 -0
- public/mc.png +3 -0
- scripts/check-node-version.mjs +16 -0
- scripts/deploy-standalone.sh +251 -0
- scripts/e2e-openclaw/start-e2e-server.mjs +44 -2
- scripts/generate-env.sh +81 -0
- scripts/security-audit.sh +168 -0
- scripts/smoke-staging.mjs +168 -0
- scripts/start-standalone.sh +33 -0
- scripts/station-doctor.sh +189 -0
- skills/mission-control-installer/README.md +68 -0
- skills/mission-control-installer/skill.json +27 -0
- skills/mission-control-manage/README.md +104 -0
- skills/mission-control-manage/skill.json +20 -0
- src/app/[[...panel]]/page.tsx +369 -81
- src/app/api/activities/route.ts +16 -4
- src/app/api/adapters/route.ts +118 -0
- src/app/api/agents/[id]/files/route.ts +153 -0
- src/app/api/agents/[id]/heartbeat/route.ts +4 -0
- src/app/api/agents/[id]/memory/route.ts +65 -4
- src/app/api/agents/[id]/route.ts +72 -26
- src/app/api/agents/[id]/soul/route.ts +9 -9
|
@@ -67,7 +67,8 @@ NEXT_PUBLIC_GATEWAY_HOST=
|
|
| 67 |
NEXT_PUBLIC_GATEWAY_PORT=18789
|
| 68 |
NEXT_PUBLIC_GATEWAY_PROTOCOL=
|
| 69 |
NEXT_PUBLIC_GATEWAY_URL=
|
| 70 |
-
#
|
|
|
|
| 71 |
# Gateway client id used in websocket handshake (role=operator UI client).
|
| 72 |
NEXT_PUBLIC_GATEWAY_CLIENT_ID=openclaw-control-ui
|
| 73 |
|
|
|
|
| 67 |
NEXT_PUBLIC_GATEWAY_PORT=18789
|
| 68 |
NEXT_PUBLIC_GATEWAY_PROTOCOL=
|
| 69 |
NEXT_PUBLIC_GATEWAY_URL=
|
| 70 |
+
# Do not expose gateway tokens via NEXT_PUBLIC_* variables.
|
| 71 |
+
# Keep gateway auth secrets server-side only (OPENCLAW_GATEWAY_TOKEN / GATEWAY_TOKEN).
|
| 72 |
# Gateway client id used in websocket handshake (role=operator UI client).
|
| 73 |
NEXT_PUBLIC_GATEWAY_CLIENT_ID=openclaw-control-ui
|
| 74 |
|
|
@@ -24,7 +24,7 @@ jobs:
|
|
| 24 |
- name: Setup Node
|
| 25 |
uses: actions/setup-node@v4
|
| 26 |
with:
|
| 27 |
-
node-version:
|
| 28 |
cache: 'pnpm'
|
| 29 |
|
| 30 |
- name: Install dependencies
|
|
|
|
| 24 |
- name: Setup Node
|
| 25 |
uses: actions/setup-node@v4
|
| 26 |
with:
|
| 27 |
+
node-version-file: '.nvmrc'
|
| 28 |
cache: 'pnpm'
|
| 29 |
|
| 30 |
- name: Install dependencies
|
|
@@ -35,6 +35,12 @@ aegis/
|
|
| 35 |
# Playwright
|
| 36 |
test-results/
|
| 37 |
playwright-report/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
# Claude Code context files
|
| 40 |
CLAUDE.md
|
|
|
|
| 35 |
# Playwright
|
| 36 |
test-results/
|
| 37 |
playwright-report/
|
| 38 |
+
.tmp/
|
| 39 |
+
.playwright-mcp/
|
| 40 |
+
|
| 41 |
+
# Local QA screenshots
|
| 42 |
+
/e2e-debug-*.png
|
| 43 |
+
/e2e-channels-*.png
|
| 44 |
|
| 45 |
# Claude Code context files
|
| 46 |
CLAUDE.md
|
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
22
|
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
22
|
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
FROM node:
|
| 2 |
RUN corepack enable && corepack prepare pnpm@latest --activate
|
| 3 |
WORKDIR /app
|
| 4 |
|
|
@@ -20,7 +20,14 @@ COPY --from=deps /app/node_modules ./node_modules
|
|
| 20 |
COPY . .
|
| 21 |
RUN pnpm build
|
| 22 |
|
| 23 |
-
FROM node:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
WORKDIR /app
|
| 25 |
ENV NODE_ENV=production
|
| 26 |
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
|
|
@@ -30,11 +37,11 @@ COPY --from=build /app/.next/static ./.next/static
|
|
| 30 |
COPY --from=build /app/src/lib/schema.sql ./src/lib/schema.sql
|
| 31 |
# Create data directory with correct ownership for SQLite
|
| 32 |
RUN mkdir -p .data && chown nextjs:nodejs .data
|
| 33 |
-
RUN
|
| 34 |
USER nextjs
|
| 35 |
ENV PORT=3000
|
| 36 |
EXPOSE 3000
|
| 37 |
ENV HOSTNAME=0.0.0.0
|
| 38 |
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
| 39 |
-
CMD
|
| 40 |
CMD ["node", "server.js"]
|
|
|
|
| 1 |
+
FROM node:22.22.0-slim AS base
|
| 2 |
RUN corepack enable && corepack prepare pnpm@latest --activate
|
| 3 |
WORKDIR /app
|
| 4 |
|
|
|
|
| 20 |
COPY . .
|
| 21 |
RUN pnpm build
|
| 22 |
|
| 23 |
+
FROM node:22.22.0-slim AS runtime
|
| 24 |
+
|
| 25 |
+
ARG MC_VERSION=dev
|
| 26 |
+
LABEL org.opencontainers.image.source="https://github.com/openclaw/mission-control"
|
| 27 |
+
LABEL org.opencontainers.image.description="Mission Control - operations dashboard"
|
| 28 |
+
LABEL org.opencontainers.image.licenses="MIT"
|
| 29 |
+
LABEL org.opencontainers.image.version="${MC_VERSION}"
|
| 30 |
+
|
| 31 |
WORKDIR /app
|
| 32 |
ENV NODE_ENV=production
|
| 33 |
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
|
|
|
|
| 37 |
COPY --from=build /app/src/lib/schema.sql ./src/lib/schema.sql
|
| 38 |
# Create data directory with correct ownership for SQLite
|
| 39 |
RUN mkdir -p .data && chown nextjs:nodejs .data
|
| 40 |
+
RUN echo 'const http=require("http");const r=http.get("http://localhost:"+(process.env.PORT||3000)+"/api/status?action=health",s=>{process.exit(s.statusCode===200?0:1)});r.on("error",()=>process.exit(1));r.setTimeout(4000,()=>{r.destroy();process.exit(1)})' > /app/healthcheck.js
|
| 41 |
USER nextjs
|
| 42 |
ENV PORT=3000
|
| 43 |
EXPOSE 3000
|
| 44 |
ENV HOSTNAME=0.0.0.0
|
| 45 |
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
| 46 |
+
CMD ["node", "/app/healthcheck.js"]
|
| 47 |
CMD ["node", "server.js"]
|
|
@@ -24,20 +24,47 @@ Manage agent fleets, track tasks, monitor costs, and orchestrate workflows — a
|
|
| 24 |
|
| 25 |
Running AI agents at scale means juggling sessions, tasks, costs, and reliability across multiple models and channels. Mission Control gives you:
|
| 26 |
|
| 27 |
-
- **
|
| 28 |
- **Real-time everything** — WebSocket + SSE push updates, smart polling that pauses when you're away
|
| 29 |
- **Zero external dependencies** — SQLite database, single `pnpm start` to run, no Redis/Postgres/Docker required
|
| 30 |
- **Role-based access** — Viewer, operator, and admin roles with session + API key auth
|
| 31 |
-
- **Quality gates** — Built-in review system that blocks task completion without sign-off
|
|
|
|
|
|
|
|
|
|
| 32 |
- **Multi-gateway** — Connect to multiple agent gateways simultaneously (OpenClaw, and more coming soon)
|
| 33 |
|
| 34 |
## Quick Start
|
| 35 |
|
| 36 |
-
|
| 37 |
|
| 38 |
```bash
|
| 39 |
git clone https://github.com/builderz-labs/mission-control.git
|
| 40 |
cd mission-control
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
pnpm install
|
| 42 |
cp .env.example .env # edit with your values
|
| 43 |
pnpm dev # http://localhost:3000
|
|
@@ -46,6 +73,25 @@ pnpm dev # http://localhost:3000
|
|
| 46 |
Initial login is seeded from `AUTH_USER` / `AUTH_PASS` on first run.
|
| 47 |
If `AUTH_PASS` contains `#`, quote it (e.g. `AUTH_PASS="my#password"`) or use `AUTH_PASS_B64`.
|
| 48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
## Project Status
|
| 50 |
|
| 51 |
### What Works
|
|
@@ -65,7 +111,22 @@ If `AUTH_PASS` contains `#`, quote it (e.g. `AUTH_PASS="my#password"`) or use `A
|
|
| 65 |
- Ed25519 device identity for secure gateway handshake
|
| 66 |
- Agent SOUL system with workspace file sync and templates
|
| 67 |
- Agent inter-agent messaging and comms
|
| 68 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
### Known Limitations
|
| 71 |
|
|
@@ -81,10 +142,10 @@ If `AUTH_PASS` contains `#`, quote it (e.g. `AUTH_PASS="my#password"`) or use `A
|
|
| 81 |
## Features
|
| 82 |
|
| 83 |
### Agent Management
|
| 84 |
-
Monitor agent status,
|
| 85 |
|
| 86 |
### Task Board
|
| 87 |
-
Kanban board with six columns (inbox →
|
| 88 |
|
| 89 |
### Real-time Monitoring
|
| 90 |
Live activity feed, session inspector, and log viewer with filtering. WebSocket connection to OpenClaw gateway for instant event delivery.
|
|
@@ -93,7 +154,10 @@ Live activity feed, session inspector, and log viewer with filtering. WebSocket
|
|
| 93 |
Token usage dashboard with per-model breakdowns, trend charts, and cost analysis powered by Recharts.
|
| 94 |
|
| 95 |
### Background Automation
|
| 96 |
-
Scheduled tasks for database backups, stale record cleanup,
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
### Direct CLI Integration
|
| 99 |
Connect Claude Code, Codex, or any CLI tool directly to Mission Control without requiring a gateway. Register connections, send heartbeats with inline token reporting, and auto-register agents.
|
|
@@ -101,28 +165,52 @@ Connect Claude Code, Codex, or any CLI tool directly to Mission Control without
|
|
| 101 |
### Claude Code Session Tracking
|
| 102 |
Automatically discovers and tracks local Claude Code sessions by scanning `~/.claude/projects/`. Extracts token usage, model info, message counts, cost estimates, and active status from JSONL transcripts. Scans every 60 seconds via the background scheduler.
|
| 103 |
|
|
|
|
|
|
|
|
|
|
| 104 |
### GitHub Issues Sync
|
| 105 |
Inbound sync from GitHub repositories with label and assignee mapping. Synced issues appear on the task board alongside agent-created tasks.
|
| 106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
### Agent SOUL System
|
| 108 |
Define agent personality, capabilities, and behavioral guidelines via SOUL markdown files. Edit in the UI or directly in workspace `soul.md` files — changes sync bidirectionally between disk and database.
|
| 109 |
|
| 110 |
### Agent Messaging
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
### Integrations
|
| 114 |
Outbound webhooks with delivery history, configurable alert rules with cooldowns, and multi-gateway connection management. Optional 1Password CLI integration for secret management.
|
| 115 |
|
| 116 |
### Workspace Management
|
| 117 |
-
Workspaces (tenant instances) are
|
| 118 |
- **Create** new client instances (slug, display name, Linux user, gateway port, plan tier)
|
| 119 |
- **Monitor** provisioning jobs and their step-by-step progress
|
| 120 |
- **Decommission** tenants with optional cleanup of state directories and Linux users
|
| 121 |
|
| 122 |
-
Each workspace gets its own isolated environment with a dedicated OpenClaw gateway, state directory, and workspace root.
|
| 123 |
|
| 124 |
### Update Checker
|
| 125 |
-
Automatic GitHub release check notifies you when a new version is available, displayed as a banner in the dashboard.
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
## Architecture
|
| 128 |
|
|
@@ -133,22 +221,35 @@ mission-control/
|
|
| 133 |
│ ├── app/
|
| 134 |
│ │ ├── page.tsx # SPA shell — routes all panels
|
| 135 |
│ │ ├── login/page.tsx # Login page
|
| 136 |
-
│ │ └── api/ #
|
| 137 |
│ ├── components/
|
| 138 |
│ │ ├── layout/ # NavRail, HeaderBar, LiveFeed
|
| 139 |
│ │ ├── dashboard/ # Overview dashboard
|
| 140 |
-
│ │ ├── panels/ #
|
| 141 |
│ │ └── chat/ # Agent chat UI
|
| 142 |
│ ├── lib/
|
| 143 |
│ │ ├── auth.ts # Session + API key auth, RBAC
|
| 144 |
│ │ ├── db.ts # SQLite (better-sqlite3, WAL mode)
|
| 145 |
│ │ ├── claude-sessions.ts # Local Claude Code session scanner
|
| 146 |
-
│ │ ├──
|
|
|
|
|
|
|
|
|
|
| 147 |
│ │ ├── scheduler.ts # Background task scheduler
|
| 148 |
│ │ ├── webhooks.ts # Outbound webhook delivery
|
| 149 |
│ │ ├── websocket.ts # Gateway WebSocket client
|
| 150 |
│ │ ├── device-identity.ts # Ed25519 device identity for gateway auth
|
| 151 |
-
│ │
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
│ └── store/index.ts # Zustand state management
|
| 153 |
└── .data/ # Runtime data (SQLite DB, token logs)
|
| 154 |
```
|
|
@@ -166,7 +267,7 @@ mission-control/
|
|
| 166 |
| Real-time | WebSocket + Server-Sent Events |
|
| 167 |
| Auth | scrypt hashing, session tokens, RBAC |
|
| 168 |
| Validation | Zod 4 |
|
| 169 |
-
| Testing | Vitest + Playwright (
|
| 170 |
|
| 171 |
## Authentication
|
| 172 |
|
|
@@ -211,7 +312,9 @@ All endpoints require authentication unless noted. Full reference below.
|
|
| 211 |
| `POST` | `/api/agents` | operator | Register/update agent |
|
| 212 |
| `GET` | `/api/agents/[id]` | viewer | Agent details |
|
| 213 |
| `GET` | `/api/agents/[id]/attribution` | viewer | Self-scope attribution/audit/cost report (`?privileged=1` admin override) |
|
| 214 |
-
| `POST` | `/api/agents/sync` | operator | Sync agents from openclaw.json |
|
|
|
|
|
|
|
| 215 |
| `GET/PUT` | `/api/agents/[id]/soul` | operator | Agent SOUL content (reads from workspace, writes to both) |
|
| 216 |
| `GET/POST` | `/api/agents/comms` | operator | Agent inter-agent communication |
|
| 217 |
| `POST` | `/api/agents/message` | operator | Send message to agent |
|
|
@@ -235,6 +338,19 @@ All endpoints require authentication unless noted. Full reference below.
|
|
| 235 |
- `hours`: integer window `1..720` (default `24`)
|
| 236 |
- `section`: comma-separated subset of `identity,audit,mutations,cost` (default all)
|
| 237 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
<details>
|
| 239 |
<summary><strong>Monitoring</strong></summary>
|
| 240 |
|
|
@@ -259,6 +375,7 @@ All endpoints require authentication unless noted. Full reference below.
|
|
| 259 |
| `GET/PUT` | `/api/settings` | admin | App settings |
|
| 260 |
| `GET/PUT` | `/api/gateway-config` | admin | OpenClaw gateway config |
|
| 261 |
| `GET/POST` | `/api/cron` | admin | Cron management |
|
|
|
|
| 262 |
|
| 263 |
</details>
|
| 264 |
|
|
@@ -297,7 +414,7 @@ All endpoints require authentication unless noted. Full reference below.
|
|
| 297 |
</details>
|
| 298 |
|
| 299 |
<details>
|
| 300 |
-
<summary><strong>
|
| 301 |
|
| 302 |
| Method | Path | Role | Description |
|
| 303 |
|--------|------|------|-------------|
|
|
@@ -310,6 +427,23 @@ All endpoints require authentication unless noted. Full reference below.
|
|
| 310 |
|
| 311 |
</details>
|
| 312 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
<details>
|
| 314 |
<summary><strong>Direct CLI</strong></summary>
|
| 315 |
|
|
@@ -351,6 +485,8 @@ All endpoints require authentication unless noted. Full reference below.
|
|
| 351 |
|--------|------|------|-------------|
|
| 352 |
| `GET` | `/api/claude/sessions` | viewer | List discovered sessions (filter: `?active=1`, `?project=`) |
|
| 353 |
| `POST` | `/api/claude/sessions` | operator | Trigger manual session scan |
|
|
|
|
|
|
|
| 354 |
|
| 355 |
</details>
|
| 356 |
|
|
@@ -402,15 +538,11 @@ See [`.env.example`](.env.example) for the complete list. Key variables:
|
|
| 402 |
|
| 403 |
### Workspace Creation Flow
|
| 404 |
|
| 405 |
-
To add a new workspace/client instance
|
| 406 |
|
| 407 |
-
1.
|
| 408 |
-
2.
|
| 409 |
-
3.
|
| 410 |
-
4. Click `Create + Queue`.
|
| 411 |
-
5. Approve/run the generated provisioning job in the same panel.
|
| 412 |
-
|
| 413 |
-
`Workspaces` and `Super Admin` currently point to the same provisioning control plane.
|
| 414 |
|
| 415 |
### Projects and Ticket Prefixes
|
| 416 |
|
|
@@ -529,8 +661,16 @@ See [open issues](https://github.com/builderz-labs/mission-control/issues) for p
|
|
| 529 |
|
| 530 |
**Up next:**
|
| 531 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 532 |
- [ ] Agent-agnostic gateway support — connect any orchestration framework (OpenClaw, ZeroClaw, OpenFang, NeoBot, IronClaw, etc.), not just OpenClaw
|
| 533 |
-
- [ ] Workspace isolation for multi-team usage ([#75](https://github.com/builderz-labs/mission-control/issues/75))
|
| 534 |
- [ ] **[Flight Deck](https://github.com/splitlabs/flight-deck)** — native desktop companion app (Tauri v2) with real PTY terminal grid, stall inbox with native OS notifications, and system tray HUD. Currently in private beta.
|
| 535 |
- [ ] First-class per-agent cost breakdowns — dedicated panel with per-agent token usage and spend (currently derivable from per-session data)
|
| 536 |
- [ ] OAuth approval UI improvements
|
|
@@ -544,6 +684,16 @@ Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup inst
|
|
| 544 |
|
| 545 |
To report a vulnerability, see [SECURITY.md](SECURITY.md).
|
| 546 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 547 |
## License
|
| 548 |
|
| 549 |
[MIT](LICENSE) © 2026 [Builderz Labs](https://github.com/builderz-labs)
|
|
|
|
| 24 |
|
| 25 |
Running AI agents at scale means juggling sessions, tasks, costs, and reliability across multiple models and channels. Mission Control gives you:
|
| 26 |
|
| 27 |
+
- **32 panels** — Tasks, agents, skills, logs, tokens, memory, security, cron, alerts, webhooks, pipelines, and more
|
| 28 |
- **Real-time everything** — WebSocket + SSE push updates, smart polling that pauses when you're away
|
| 29 |
- **Zero external dependencies** — SQLite database, single `pnpm start` to run, no Redis/Postgres/Docker required
|
| 30 |
- **Role-based access** — Viewer, operator, and admin roles with session + API key auth
|
| 31 |
+
- **Quality gates** — Built-in Aegis review system that blocks task completion without sign-off
|
| 32 |
+
- **Recurring tasks** — Natural language scheduling ("every morning at 9am") with cron-based template spawning
|
| 33 |
+
- **Claude Code bridge** — Read-only integration surfaces Claude Code team tasks and configs on the dashboard
|
| 34 |
+
- **Skills Hub** — Browse, install, and security-scan agent skills from ClawdHub and skills.sh registries
|
| 35 |
- **Multi-gateway** — Connect to multiple agent gateways simultaneously (OpenClaw, and more coming soon)
|
| 36 |
|
| 37 |
## Quick Start
|
| 38 |
|
| 39 |
+
### One-Command Install (Docker)
|
| 40 |
|
| 41 |
```bash
|
| 42 |
git clone https://github.com/builderz-labs/mission-control.git
|
| 43 |
cd mission-control
|
| 44 |
+
bash install.sh --docker
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
The installer auto-generates secure credentials, starts the container, and runs an OpenClaw fleet health check. Open `http://localhost:3000` and log in with the printed credentials.
|
| 48 |
+
|
| 49 |
+
### One-Command Install (Local)
|
| 50 |
+
|
| 51 |
+
```bash
|
| 52 |
+
git clone https://github.com/builderz-labs/mission-control.git
|
| 53 |
+
cd mission-control
|
| 54 |
+
bash install.sh --local
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
Requires Node.js 22.x (LTS) and pnpm (auto-installed via corepack if missing).
|
| 58 |
+
|
| 59 |
+
### Manual Setup
|
| 60 |
+
|
| 61 |
+
> **Requires [pnpm](https://pnpm.io/installation)** and **Node.js 22.x (LTS)**.
|
| 62 |
+
> Mission Control is validated against Node 22 across local dev, CI, Docker, and standalone deploys. Use `nvm use 22` (or your version manager equivalent) before installing or starting the app.
|
| 63 |
+
|
| 64 |
+
```bash
|
| 65 |
+
git clone https://github.com/builderz-labs/mission-control.git
|
| 66 |
+
cd mission-control
|
| 67 |
+
nvm use 22
|
| 68 |
pnpm install
|
| 69 |
cp .env.example .env # edit with your values
|
| 70 |
pnpm dev # http://localhost:3000
|
|
|
|
| 73 |
Initial login is seeded from `AUTH_USER` / `AUTH_PASS` on first run.
|
| 74 |
If `AUTH_PASS` contains `#`, quote it (e.g. `AUTH_PASS="my#password"`) or use `AUTH_PASS_B64`.
|
| 75 |
|
| 76 |
+
### Docker Hardening (Production)
|
| 77 |
+
|
| 78 |
+
For production deployments, use the hardened compose overlay:
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
docker compose -f docker-compose.yml -f docker-compose.hardened.yml up -d
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
This adds read-only filesystem, capability dropping, log rotation, HSTS, and network isolation. See [Security Hardening](docs/SECURITY-HARDENING.md) for the full checklist.
|
| 85 |
+
|
| 86 |
+
### Station Doctor
|
| 87 |
+
|
| 88 |
+
Run diagnostics on your installation:
|
| 89 |
+
|
| 90 |
+
```bash
|
| 91 |
+
bash scripts/station-doctor.sh
|
| 92 |
+
bash scripts/security-audit.sh
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
## Project Status
|
| 96 |
|
| 97 |
### What Works
|
|
|
|
| 111 |
- Ed25519 device identity for secure gateway handshake
|
| 112 |
- Agent SOUL system with workspace file sync and templates
|
| 113 |
- Agent inter-agent messaging and comms
|
| 114 |
+
- Skills Hub with ClawdHub and skills.sh registry integration (search, install, security scan)
|
| 115 |
+
- Bidirectional skill sync — disk ↔ DB with SHA-256 change detection
|
| 116 |
+
- Local agent discovery from `~/.agents/`, `~/.codex/agents/`, `~/.claude/agents/`
|
| 117 |
+
- Natural language recurring tasks — schedule parser converts "every 2 hours" to cron, spawns dated child tasks
|
| 118 |
+
- Claude Code task bridge — read-only scanner surfaces team tasks and configs from `~/.claude/tasks/` and `~/.claude/teams/`
|
| 119 |
+
- Skill security scanner (prompt injection, credential leaks, data exfiltration, obfuscated content)
|
| 120 |
+
- Update available banner with GitHub release check and one-click self-update
|
| 121 |
+
- Framework adapter layer for multi-agent registration (OpenClaw, CrewAI, LangGraph, AutoGen, Claude SDK, generic)
|
| 122 |
+
- Multi-project task organization with per-project ticket prefixes
|
| 123 |
+
- Per-agent rate limiting with `x-agent-name` identity-based quotas
|
| 124 |
+
- Agent self-registration endpoint for autonomous agent onboarding
|
| 125 |
+
- Security audit panel with posture scoring, secret detection, trust scoring, and MCP call auditing
|
| 126 |
+
- Four-layer agent eval framework (output, trace, component, drift detection)
|
| 127 |
+
- Agent optimization endpoint with token efficiency, tool patterns, and fleet benchmarks
|
| 128 |
+
- Hook profiles (minimal/standard/strict) for tunable security strictness
|
| 129 |
+
- Guided onboarding wizard with credential setup, agent discovery, and security scan
|
| 130 |
|
| 131 |
### Known Limitations
|
| 132 |
|
|
|
|
| 142 |
## Features
|
| 143 |
|
| 144 |
### Agent Management
|
| 145 |
+
Monitor agent status, configure models, view heartbeats, and manage the full agent lifecycle from registration to retirement. Agent detail modal with compact overview, inline model selector, and editable sub-agent configuration.
|
| 146 |
|
| 147 |
### Task Board
|
| 148 |
+
Kanban board with six columns (inbox → assigned → in progress → review → quality review → done), drag-and-drop, priority levels, assignments, threaded comments, and inline sub-agent spawning.
|
| 149 |
|
| 150 |
### Real-time Monitoring
|
| 151 |
Live activity feed, session inspector, and log viewer with filtering. WebSocket connection to OpenClaw gateway for instant event delivery.
|
|
|
|
| 154 |
Token usage dashboard with per-model breakdowns, trend charts, and cost analysis powered by Recharts.
|
| 155 |
|
| 156 |
### Background Automation
|
| 157 |
+
Scheduled tasks for database backups, stale record cleanup, agent heartbeat monitoring, and recurring task spawning. Configurable via UI or API.
|
| 158 |
+
|
| 159 |
+
### Natural Language Recurring Tasks
|
| 160 |
+
Create recurring tasks with natural language like "every morning at 9am" or "every 2 hours". The built-in schedule parser (zero dependencies) converts expressions to cron and stores them in task metadata. A template-clone pattern keeps the original task as a template and spawns dated child tasks (e.g., "Daily Report - Mar 07") on schedule. Each spawned task gets its own Aegis quality gate.
|
| 161 |
|
| 162 |
### Direct CLI Integration
|
| 163 |
Connect Claude Code, Codex, or any CLI tool directly to Mission Control without requiring a gateway. Register connections, send heartbeats with inline token reporting, and auto-register agents.
|
|
|
|
| 165 |
### Claude Code Session Tracking
|
| 166 |
Automatically discovers and tracks local Claude Code sessions by scanning `~/.claude/projects/`. Extracts token usage, model info, message counts, cost estimates, and active status from JSONL transcripts. Scans every 60 seconds via the background scheduler.
|
| 167 |
|
| 168 |
+
### Claude Code Task Bridge
|
| 169 |
+
Read-only integration that surfaces Claude Code team tasks and team configs on the Mission Control dashboard. Scans `~/.claude/tasks/<team>/<N>.json` for structured task data (subject, status, owner, blockers) and `~/.claude/teams/<name>/config.json` for team metadata (members, lead agent, model assignments). Visible in both the Task Board (collapsible section) and Cron Management (teams overview) panels.
|
| 170 |
+
|
| 171 |
### GitHub Issues Sync
|
| 172 |
Inbound sync from GitHub repositories with label and assignee mapping. Synced issues appear on the task board alongside agent-created tasks.
|
| 173 |
|
| 174 |
+
### Skills Hub
|
| 175 |
+
Browse, install, and manage agent skills from local directories and external registries (ClawdHub, skills.sh). Bidirectional sync detects manual additions on disk and pushes UI edits back to `SKILL.md` files. Built-in security scanner checks for prompt injection, credential leaks, data exfiltration, obfuscated content, and dangerous shell commands before installation. Supports 5 skill roots: `~/.agents/skills`, `~/.codex/skills`, project-local `.agents/skills` and `.codex/skills`, and `~/.openclaw/skills` for gateway mode.
|
| 176 |
+
|
| 177 |
+
### Local Agent Discovery
|
| 178 |
+
Automatically discovers agent definitions from `~/.agents/`, `~/.codex/agents/`, and `~/.claude/agents/` directories. Detection looks for marker files (AGENT.md, soul.md, identity.md, config.json). Discovered agents sync bidirectionally — edit in the UI and changes write back to disk.
|
| 179 |
+
|
| 180 |
### Agent SOUL System
|
| 181 |
Define agent personality, capabilities, and behavioral guidelines via SOUL markdown files. Edit in the UI or directly in workspace `soul.md` files — changes sync bidirectionally between disk and database.
|
| 182 |
|
| 183 |
### Agent Messaging
|
| 184 |
+
Session-threaded inter-agent communication via the comms API (`a2a:*`, `coord:*`, `session:*`) with coordinator inbox support and runtime tool-call visibility in the `agent-comms` feed.
|
| 185 |
+
|
| 186 |
+
### Onboarding Wizard
|
| 187 |
+
Guided first-run setup wizard that walks new users through five steps: Welcome (system capabilities detection), Credentials (verify AUTH_PASS and API_KEY strength), Agent Setup (gateway connection or local Claude Code discovery), Security Scan (automated configuration audit with pass/fail checks), and Get Started (quick links to key panels). Automatically appears on first login and can be re-launched from Settings. Progress is persisted per-user so you can resume where you left off.
|
| 188 |
+
|
| 189 |
+
### Security Audit & Agent Trust
|
| 190 |
+
Dedicated security audit panel with real-time posture scoring (0-100), secret detection across agent messages, MCP tool call auditing, injection attempt tracking, and per-agent trust scores. Hook profiles (minimal/standard/strict) let operators tune security strictness per deployment. Auth failures, rate limit hits, and injection attempts are logged automatically as security events.
|
| 191 |
+
|
| 192 |
+
### Agent Eval Framework
|
| 193 |
+
Four-layer evaluation stack for agent quality: output evals (task completion scoring against golden datasets), trace evals (convergence scoring — >3.0 indicates looping), component evals (tool reliability with p50/p95/p99 latency from MCP call logs), and drift detection (10% threshold vs 4-week rolling baseline). Manage golden datasets and trigger eval runs via API or UI.
|
| 194 |
+
|
| 195 |
+
### Agent Optimization
|
| 196 |
+
API endpoint agents can call for self-improvement recommendations. Analyzes token efficiency (tokens/task vs fleet average), tool usage patterns (success/failure rates, redundant calls), and generates prioritized recommendations. Fleet benchmarks provide percentile rankings across all agents.
|
| 197 |
|
| 198 |
### Integrations
|
| 199 |
Outbound webhooks with delivery history, configurable alert rules with cooldowns, and multi-gateway connection management. Optional 1Password CLI integration for secret management.
|
| 200 |
|
| 201 |
### Workspace Management
|
| 202 |
+
Workspaces (tenant instances) are managed via the `/api/super/*` API endpoints. Admins can:
|
| 203 |
- **Create** new client instances (slug, display name, Linux user, gateway port, plan tier)
|
| 204 |
- **Monitor** provisioning jobs and their step-by-step progress
|
| 205 |
- **Decommission** tenants with optional cleanup of state directories and Linux users
|
| 206 |
|
| 207 |
+
Each workspace gets its own isolated environment with a dedicated OpenClaw gateway, state directory, and workspace root.
|
| 208 |
|
| 209 |
### Update Checker
|
| 210 |
+
Automatic GitHub release check notifies you when a new version is available, displayed as a banner in the dashboard. Admins can trigger a one-click update directly from the banner — the server runs `git pull`, `pnpm install`, and `pnpm build`, then prompts for a restart. Dirty working trees are rejected, and all updates are logged to the audit trail.
|
| 211 |
+
|
| 212 |
+
### Framework Adapters
|
| 213 |
+
Built-in adapter layer for multi-agent registration across frameworks. Supported adapters: OpenClaw, CrewAI, LangGraph, AutoGen, Claude SDK, and a generic fallback. Each adapter normalizes agent registration, heartbeats, and task reporting to a common interface.
|
| 214 |
|
| 215 |
## Architecture
|
| 216 |
|
|
|
|
| 221 |
│ ├── app/
|
| 222 |
│ │ ├── page.tsx # SPA shell — routes all panels
|
| 223 |
│ │ ├── login/page.tsx # Login page
|
| 224 |
+
│ │ └── api/ # 101 REST API routes
|
| 225 |
│ ├── components/
|
| 226 |
│ │ ├── layout/ # NavRail, HeaderBar, LiveFeed
|
| 227 |
│ │ ├── dashboard/ # Overview dashboard
|
| 228 |
+
│ │ ├── panels/ # 32 feature panels
|
| 229 |
│ │ └── chat/ # Agent chat UI
|
| 230 |
│ ├── lib/
|
| 231 |
│ │ ├── auth.ts # Session + API key auth, RBAC
|
| 232 |
│ │ ├── db.ts # SQLite (better-sqlite3, WAL mode)
|
| 233 |
│ │ ├── claude-sessions.ts # Local Claude Code session scanner
|
| 234 |
+
│ │ ├── claude-tasks.ts # Claude Code team task/config scanner
|
| 235 |
+
│ │ ├── schedule-parser.ts # Natural language → cron expression parser
|
| 236 |
+
│ │ ├── recurring-tasks.ts # Recurring task template spawner
|
| 237 |
+
│ │ ├── migrations.ts # 39 schema migrations
|
| 238 |
│ │ ├── scheduler.ts # Background task scheduler
|
| 239 |
│ │ ├── webhooks.ts # Outbound webhook delivery
|
| 240 |
│ │ ├── websocket.ts # Gateway WebSocket client
|
| 241 |
│ │ ├── device-identity.ts # Ed25519 device identity for gateway auth
|
| 242 |
+
│ │ ├── agent-sync.ts # OpenClaw config → MC database sync
|
| 243 |
+
│ │ ├── skill-sync.ts # Bidirectional disk ↔ DB skill sync
|
| 244 |
+
│ │ ├── skill-registry.ts # ClawdHub + skills.sh registry client & security scanner
|
| 245 |
+
│ │ ├── local-agent-sync.ts # Local agent discovery from ~/.agents, ~/.codex, ~/.claude
|
| 246 |
+
│ │ ├── secret-scanner.ts # Regex-based secret detection (AWS, GitHub, Stripe, JWT, PEM, DB URIs)
|
| 247 |
+
│ │ ├── security-events.ts # Security event logger + agent trust scoring
|
| 248 |
+
│ │ ├── mcp-audit.ts # MCP tool call auditing
|
| 249 |
+
│ │ ├── agent-evals.ts # Four-layer agent eval framework
|
| 250 |
+
│ │ ├── agent-optimizer.ts # Agent optimization engine
|
| 251 |
+
│ │ ├── hook-profiles.ts # Security strictness profiles (minimal/standard/strict)
|
| 252 |
+
│ │ └── adapters/ # Framework adapters (openclaw, crewai, langgraph, autogen, claude-sdk, generic)
|
| 253 |
│ └── store/index.ts # Zustand state management
|
| 254 |
└── .data/ # Runtime data (SQLite DB, token logs)
|
| 255 |
```
|
|
|
|
| 267 |
| Real-time | WebSocket + Server-Sent Events |
|
| 268 |
| Auth | scrypt hashing, session tokens, RBAC |
|
| 269 |
| Validation | Zod 4 |
|
| 270 |
+
| Testing | Vitest (282 unit) + Playwright (295 E2E) |
|
| 271 |
|
| 272 |
## Authentication
|
| 273 |
|
|
|
|
| 312 |
| `POST` | `/api/agents` | operator | Register/update agent |
|
| 313 |
| `GET` | `/api/agents/[id]` | viewer | Agent details |
|
| 314 |
| `GET` | `/api/agents/[id]/attribution` | viewer | Self-scope attribution/audit/cost report (`?privileged=1` admin override) |
|
| 315 |
+
| `POST` | `/api/agents/sync` | operator | Sync agents from openclaw.json or local disk (`?source=local`) |
|
| 316 |
+
| `POST` | `/api/agents/register` | viewer | Agent self-registration (idempotent, rate-limited) |
|
| 317 |
+
| `GET/POST` | `/api/adapters` | viewer/operator | List adapters / Framework-agnostic agent action dispatch |
|
| 318 |
| `GET/PUT` | `/api/agents/[id]/soul` | operator | Agent SOUL content (reads from workspace, writes to both) |
|
| 319 |
| `GET/POST` | `/api/agents/comms` | operator | Agent inter-agent communication |
|
| 320 |
| `POST` | `/api/agents/message` | operator | Send message to agent |
|
|
|
|
| 338 |
- `hours`: integer window `1..720` (default `24`)
|
| 339 |
- `section`: comma-separated subset of `identity,audit,mutations,cost` (default all)
|
| 340 |
|
| 341 |
+
<details>
|
| 342 |
+
<summary><strong>Security & Evals</strong></summary>
|
| 343 |
+
|
| 344 |
+
| Method | Path | Role | Description |
|
| 345 |
+
|--------|------|------|-------------|
|
| 346 |
+
| `GET` | `/api/security-audit` | admin | Security posture, events, trust scores, MCP audit (`?timeframe=day`) |
|
| 347 |
+
| `GET` | `/api/security-scan` | admin | Static security configuration scan |
|
| 348 |
+
| `GET` | `/api/agents/optimize` | operator | Agent optimization recommendations (`?agent=&hours=24`) |
|
| 349 |
+
| `GET` | `/api/agents/evals` | operator | Agent eval results (`?agent=`, `?action=history&weeks=4`) |
|
| 350 |
+
| `POST` | `/api/agents/evals` | operator | Trigger eval run (`action: 'run'`) or manage golden datasets (`action: 'golden-set'`) |
|
| 351 |
+
|
| 352 |
+
</details>
|
| 353 |
+
|
| 354 |
<details>
|
| 355 |
<summary><strong>Monitoring</strong></summary>
|
| 356 |
|
|
|
|
| 375 |
| `GET/PUT` | `/api/settings` | admin | App settings |
|
| 376 |
| `GET/PUT` | `/api/gateway-config` | admin | OpenClaw gateway config |
|
| 377 |
| `GET/POST` | `/api/cron` | admin | Cron management |
|
| 378 |
+
| `GET/POST` | `/api/onboarding` | viewer | Onboarding wizard state and step progression |
|
| 379 |
|
| 380 |
</details>
|
| 381 |
|
|
|
|
| 414 |
</details>
|
| 415 |
|
| 416 |
<details>
|
| 417 |
+
<summary><strong>Workspace/Tenant Management</strong></summary>
|
| 418 |
|
| 419 |
| Method | Path | Role | Description |
|
| 420 |
|--------|------|------|-------------|
|
|
|
|
| 427 |
|
| 428 |
</details>
|
| 429 |
|
| 430 |
+
<details>
|
| 431 |
+
<summary><strong>Skills</strong></summary>
|
| 432 |
+
|
| 433 |
+
| Method | Path | Role | Description |
|
| 434 |
+
|--------|------|------|-------------|
|
| 435 |
+
| `GET` | `/api/skills` | viewer | List skills (DB-backed with filesystem fallback) |
|
| 436 |
+
| `GET` | `/api/skills?mode=content&source=…&name=…` | viewer | Read SKILL.md content with inline security report |
|
| 437 |
+
| `GET` | `/api/skills?mode=check&source=…&name=…` | viewer | On-demand security scan |
|
| 438 |
+
| `POST` | `/api/skills` | operator | Create skill |
|
| 439 |
+
| `PUT` | `/api/skills` | operator | Update skill content |
|
| 440 |
+
| `DELETE` | `/api/skills` | operator | Delete skill |
|
| 441 |
+
| `GET` | `/api/skills/registry?source=…&q=…` | viewer | Search external registry (ClawdHub, skills.sh) |
|
| 442 |
+
| `POST` | `/api/skills/registry` | admin | Install skill from registry |
|
| 443 |
+
| `PUT` | `/api/skills/registry` | viewer | Security-check content without installing |
|
| 444 |
+
|
| 445 |
+
</details>
|
| 446 |
+
|
| 447 |
<details>
|
| 448 |
<summary><strong>Direct CLI</strong></summary>
|
| 449 |
|
|
|
|
| 485 |
|--------|------|------|-------------|
|
| 486 |
| `GET` | `/api/claude/sessions` | viewer | List discovered sessions (filter: `?active=1`, `?project=`) |
|
| 487 |
| `POST` | `/api/claude/sessions` | operator | Trigger manual session scan |
|
| 488 |
+
| `GET` | `/api/claude-tasks` | viewer | List Claude Code team tasks and configs (`?force=true` to bypass cache) |
|
| 489 |
+
| `GET` | `/api/schedule-parse` | viewer | Parse natural language schedule (`?input=every+2+hours`) |
|
| 490 |
|
| 491 |
</details>
|
| 492 |
|
|
|
|
| 538 |
|
| 539 |
### Workspace Creation Flow
|
| 540 |
|
| 541 |
+
To add a new workspace/client instance, use the `/api/super/tenants` endpoint or the Workspaces panel (if enabled):
|
| 542 |
|
| 543 |
+
1. Provide tenant/workspace fields (`slug`, `display_name`, optional ports/gateway owner).
|
| 544 |
+
2. The system queues a bootstrap provisioning job.
|
| 545 |
+
3. Approve/run the provisioning job via `/api/super/provision-jobs/[id]/action`.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 546 |
|
| 547 |
### Projects and Ticket Prefixes
|
| 548 |
|
|
|
|
| 661 |
|
| 662 |
**Up next:**
|
| 663 |
|
| 664 |
+
- [x] Workspace isolation for multi-team usage ([#75](https://github.com/builderz-labs/mission-control/issues/75))
|
| 665 |
+
- [x] Framework adapter layer — multi-agent registration across OpenClaw, CrewAI, LangGraph, AutoGen, Claude SDK, and generic
|
| 666 |
+
- [x] Self-update mechanism — admin-only one-click update with audit logging
|
| 667 |
+
- [x] Multi-project task organization with per-project ticket prefixes
|
| 668 |
+
- [x] Skills Hub — browse, install, and security-scan skills from ClawdHub and skills.sh registries
|
| 669 |
+
- [x] Bidirectional skill sync — disk ↔ DB with SHA-256 change detection (60s scheduler)
|
| 670 |
+
- [x] Local agent discovery — auto-detect agents from `~/.agents/`, `~/.codex/agents/`, `~/.claude/agents/`
|
| 671 |
+
- [x] Natural language recurring tasks with cron-based template spawning
|
| 672 |
+
- [x] Claude Code task bridge — read-only team task and config integration
|
| 673 |
- [ ] Agent-agnostic gateway support — connect any orchestration framework (OpenClaw, ZeroClaw, OpenFang, NeoBot, IronClaw, etc.), not just OpenClaw
|
|
|
|
| 674 |
- [ ] **[Flight Deck](https://github.com/splitlabs/flight-deck)** — native desktop companion app (Tauri v2) with real PTY terminal grid, stall inbox with native OS notifications, and system tray HUD. Currently in private beta.
|
| 675 |
- [ ] First-class per-agent cost breakdowns — dedicated panel with per-agent token usage and spend (currently derivable from per-session data)
|
| 676 |
- [ ] OAuth approval UI improvements
|
|
|
|
| 684 |
|
| 685 |
To report a vulnerability, see [SECURITY.md](SECURITY.md).
|
| 686 |
|
| 687 |
+
## ❤️ Support the Project
|
| 688 |
+
|
| 689 |
+
If you find this project useful, consider supporting my open-source work.
|
| 690 |
+
|
| 691 |
+
[](https://buymeacoffee.com/nyk_builderz)
|
| 692 |
+
|
| 693 |
+
**Solana donations**
|
| 694 |
+
|
| 695 |
+
`BYLu8XD8hGDUtdRBWpGWu5HKoiPrWqCxYFSh4oxXuvPg`
|
| 696 |
+
|
| 697 |
## License
|
| 698 |
|
| 699 |
[MIT](LICENSE) © 2026 [Builderz Labs](https://github.com/builderz-labs)
|
|
@@ -27,5 +27,42 @@ Mission Control handles authentication credentials and API keys. When deploying:
|
|
| 27 |
- Always set strong values for `AUTH_PASS` and `API_KEY`.
|
| 28 |
- Use `MC_ALLOWED_HOSTS` to restrict network access in production.
|
| 29 |
- Keep `.env` files out of version control (already in `.gitignore`).
|
| 30 |
-
- Enable `MC_COOKIE_SECURE=
|
| 31 |
- Review the [Environment Variables](README.md#environment-variables) section for all security-relevant configuration.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
- Always set strong values for `AUTH_PASS` and `API_KEY`.
|
| 28 |
- Use `MC_ALLOWED_HOSTS` to restrict network access in production.
|
| 29 |
- Keep `.env` files out of version control (already in `.gitignore`).
|
| 30 |
+
- Enable `MC_COOKIE_SECURE=1` when serving over HTTPS.
|
| 31 |
- Review the [Environment Variables](README.md#environment-variables) section for all security-relevant configuration.
|
| 32 |
+
|
| 33 |
+
## Hardening Checklist
|
| 34 |
+
|
| 35 |
+
Run `bash scripts/security-audit.sh` to check your deployment automatically.
|
| 36 |
+
|
| 37 |
+
### Credentials
|
| 38 |
+
- [ ] `AUTH_PASS` is a strong, unique password (12+ characters)
|
| 39 |
+
- [ ] `API_KEY` is a random hex string (not the default)
|
| 40 |
+
- [ ] `AUTH_SECRET` is a random string
|
| 41 |
+
- [ ] `.env` file permissions are `600` (owner read/write only)
|
| 42 |
+
|
| 43 |
+
### Network
|
| 44 |
+
- [ ] `MC_ALLOWED_HOSTS` is configured (not `MC_ALLOW_ANY_HOST=1`)
|
| 45 |
+
- [ ] Dashboard is behind a reverse proxy with TLS (Caddy, nginx, Tailscale)
|
| 46 |
+
- [ ] `MC_ENABLE_HSTS=1` is set for HTTPS deployments
|
| 47 |
+
- [ ] `MC_COOKIE_SECURE=1` is set for HTTPS deployments
|
| 48 |
+
- [ ] `MC_COOKIE_SAMESITE=strict`
|
| 49 |
+
|
| 50 |
+
### Docker (if applicable)
|
| 51 |
+
- [ ] Use the hardened compose overlay: `docker compose -f docker-compose.yml -f docker-compose.hardened.yml up`
|
| 52 |
+
- [ ] Container runs as non-root user (default: `nextjs`, UID 1001)
|
| 53 |
+
- [ ] Read-only filesystem with tmpfs for temp dirs
|
| 54 |
+
- [ ] All Linux capabilities dropped except `NET_BIND_SERVICE`
|
| 55 |
+
- [ ] `no-new-privileges` security option enabled
|
| 56 |
+
- [ ] Log rotation configured (max-size, max-file)
|
| 57 |
+
|
| 58 |
+
### OpenClaw Gateway
|
| 59 |
+
- [ ] Gateway bound to localhost (`OPENCLAW_GATEWAY_HOST=127.0.0.1`)
|
| 60 |
+
- [ ] Gateway token configured (`OPENCLAW_GATEWAY_TOKEN`)
|
| 61 |
+
- [ ] Gateway token NOT exposed via `NEXT_PUBLIC_*` variables
|
| 62 |
+
|
| 63 |
+
### Monitoring
|
| 64 |
+
- [ ] Rate limiting is active (`MC_DISABLE_RATE_LIMIT` is NOT set)
|
| 65 |
+
- [ ] Audit logging is enabled with appropriate retention
|
| 66 |
+
- [ ] Regular database backups configured
|
| 67 |
+
|
| 68 |
+
See [docs/SECURITY-HARDENING.md](docs/SECURITY-HARDENING.md) for the full hardening guide.
|
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: mission-control
|
| 3 |
+
description: "Interact with Mission Control — AI agent orchestration dashboard. Use when registering agents, managing tasks, syncing skills, or querying agent/task status via MC APIs."
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
# Mission Control Agent Skill
|
| 7 |
+
|
| 8 |
+
Mission Control (MC) is an AI agent orchestration dashboard with real-time SSE/WebSocket, a skill registry, framework adapters, and RBAC. This skill teaches agents how to interact with MC APIs programmatically.
|
| 9 |
+
|
| 10 |
+
## Quick Start
|
| 11 |
+
|
| 12 |
+
**Base URL:** `http://localhost:3000` (default Next.js dev) or your deployed host.
|
| 13 |
+
|
| 14 |
+
**Auth header:** `x-api-key: <your-api-key>`
|
| 15 |
+
|
| 16 |
+
**Register + heartbeat in two calls:**
|
| 17 |
+
|
| 18 |
+
```bash
|
| 19 |
+
# 1. Register
|
| 20 |
+
curl -X POST http://localhost:3000/api/adapters \
|
| 21 |
+
-H "Content-Type: application/json" \
|
| 22 |
+
-H "x-api-key: $MC_API_KEY" \
|
| 23 |
+
-d '{
|
| 24 |
+
"framework": "generic",
|
| 25 |
+
"action": "register",
|
| 26 |
+
"payload": { "agentId": "my-agent-01", "name": "My Agent" }
|
| 27 |
+
}'
|
| 28 |
+
|
| 29 |
+
# 2. Heartbeat (repeat every 5 minutes)
|
| 30 |
+
curl -X POST http://localhost:3000/api/adapters \
|
| 31 |
+
-H "Content-Type: application/json" \
|
| 32 |
+
-H "x-api-key: $MC_API_KEY" \
|
| 33 |
+
-d '{
|
| 34 |
+
"framework": "generic",
|
| 35 |
+
"action": "heartbeat",
|
| 36 |
+
"payload": { "agentId": "my-agent-01", "status": "online" }
|
| 37 |
+
}'
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
## Authentication
|
| 41 |
+
|
| 42 |
+
MC supports two auth methods:
|
| 43 |
+
|
| 44 |
+
| Method | Header | Use Case |
|
| 45 |
+
|--------|--------|----------|
|
| 46 |
+
| API Key | `x-api-key: <key>` or `Authorization: Bearer <key>` | Agents, scripts, CI/CD |
|
| 47 |
+
| Session cookie | `Cookie: mc-session=<token>` | Browser UI |
|
| 48 |
+
|
| 49 |
+
**Roles (hierarchical):** `viewer` < `operator` < `admin`
|
| 50 |
+
|
| 51 |
+
- **viewer** — Read-only access (GET endpoints)
|
| 52 |
+
- **operator** — Create/update agents, tasks, skills, use adapters
|
| 53 |
+
- **admin** — Full access including user management
|
| 54 |
+
|
| 55 |
+
API key auth grants `admin` role by default. The key is set via `API_KEY` env var or the `security.api_key` DB setting.
|
| 56 |
+
|
| 57 |
+
Agents can identify themselves with the optional `X-Agent-Name` header for attribution in audit logs.
|
| 58 |
+
|
| 59 |
+
## Agent Lifecycle
|
| 60 |
+
|
| 61 |
+
```
|
| 62 |
+
register → heartbeat (5m interval) → fetch assignments → report task status → disconnect
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
All lifecycle actions go through the adapter protocol (`POST /api/adapters`).
|
| 66 |
+
|
| 67 |
+
### 1. Register
|
| 68 |
+
|
| 69 |
+
```json
|
| 70 |
+
{
|
| 71 |
+
"framework": "generic",
|
| 72 |
+
"action": "register",
|
| 73 |
+
"payload": {
|
| 74 |
+
"agentId": "my-agent-01",
|
| 75 |
+
"name": "My Agent",
|
| 76 |
+
"metadata": { "version": "1.0", "capabilities": ["code", "review"] }
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
### 2. Heartbeat
|
| 82 |
+
|
| 83 |
+
Send every ~5 minutes to stay marked as online.
|
| 84 |
+
|
| 85 |
+
```json
|
| 86 |
+
{
|
| 87 |
+
"framework": "generic",
|
| 88 |
+
"action": "heartbeat",
|
| 89 |
+
"payload": {
|
| 90 |
+
"agentId": "my-agent-01",
|
| 91 |
+
"status": "online",
|
| 92 |
+
"metrics": { "tasks_completed": 5, "uptime_seconds": 3600 }
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
### 3. Fetch Assignments
|
| 98 |
+
|
| 99 |
+
Returns up to 5 pending tasks sorted by priority (critical → low), then due date.
|
| 100 |
+
|
| 101 |
+
```json
|
| 102 |
+
{
|
| 103 |
+
"framework": "generic",
|
| 104 |
+
"action": "assignments",
|
| 105 |
+
"payload": { "agentId": "my-agent-01" }
|
| 106 |
+
}
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
**Response:**
|
| 110 |
+
|
| 111 |
+
```json
|
| 112 |
+
{
|
| 113 |
+
"assignments": [
|
| 114 |
+
{ "taskId": "42", "description": "Fix login bug\nUsers cannot log in with SSO", "priority": 1 }
|
| 115 |
+
],
|
| 116 |
+
"framework": "generic"
|
| 117 |
+
}
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
### 4. Report Task Progress
|
| 121 |
+
|
| 122 |
+
```json
|
| 123 |
+
{
|
| 124 |
+
"framework": "generic",
|
| 125 |
+
"action": "report",
|
| 126 |
+
"payload": {
|
| 127 |
+
"taskId": "42",
|
| 128 |
+
"agentId": "my-agent-01",
|
| 129 |
+
"progress": 75,
|
| 130 |
+
"status": "in_progress",
|
| 131 |
+
"output": "Fixed SSO handler, running tests..."
|
| 132 |
+
}
|
| 133 |
+
}
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
`status` values: `in_progress`, `done`, `failed`, `blocked`
|
| 137 |
+
|
| 138 |
+
### 5. Disconnect
|
| 139 |
+
|
| 140 |
+
```json
|
| 141 |
+
{
|
| 142 |
+
"framework": "generic",
|
| 143 |
+
"action": "disconnect",
|
| 144 |
+
"payload": { "agentId": "my-agent-01" }
|
| 145 |
+
}
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
## Core API Reference
|
| 149 |
+
|
| 150 |
+
### Agents — `/api/agents`
|
| 151 |
+
|
| 152 |
+
| Method | Min Role | Description |
|
| 153 |
+
|--------|----------|-------------|
|
| 154 |
+
| GET | viewer | List agents. Query: `?status=online&role=dev&limit=50&offset=0` |
|
| 155 |
+
| POST | operator | Create agent. Body: `{ name, role, status?, config?, template?, session_key?, soul_content? }` |
|
| 156 |
+
| PUT | operator | Update agent. Body: `{ name, status?, role?, config?, session_key?, soul_content?, last_activity? }` |
|
| 157 |
+
|
| 158 |
+
**GET response shape:**
|
| 159 |
+
|
| 160 |
+
```json
|
| 161 |
+
{
|
| 162 |
+
"agents": [{
|
| 163 |
+
"id": 1, "name": "scout", "role": "researcher", "status": "online",
|
| 164 |
+
"config": {}, "taskStats": { "total": 10, "assigned": 2, "in_progress": 1, "completed": 7 }
|
| 165 |
+
}],
|
| 166 |
+
"total": 1, "page": 1, "limit": 50
|
| 167 |
+
}
|
| 168 |
+
```
|
| 169 |
+
|
| 170 |
+
### Tasks — `/api/tasks`
|
| 171 |
+
|
| 172 |
+
| Method | Min Role | Description |
|
| 173 |
+
|--------|----------|-------------|
|
| 174 |
+
| GET | viewer | List tasks. Query: `?status=in_progress&assigned_to=scout&priority=high&project_id=1&limit=50&offset=0` |
|
| 175 |
+
| POST | operator | Create task. Body: `{ title, description?, status?, priority?, assigned_to?, project_id?, tags?, metadata?, due_date?, estimated_hours? }` |
|
| 176 |
+
| PUT | operator | Bulk status update. Body: `{ tasks: [{ id, status }] }` |
|
| 177 |
+
|
| 178 |
+
**Priority values:** `critical`, `high`, `medium`, `low`
|
| 179 |
+
|
| 180 |
+
**Status values:** `inbox`, `assigned`, `in_progress`, `review`, `done`, `failed`, `blocked`, `cancelled`
|
| 181 |
+
|
| 182 |
+
Note: Moving a task to `done` via PUT requires an Aegis quality review approval.
|
| 183 |
+
|
| 184 |
+
**POST response:**
|
| 185 |
+
|
| 186 |
+
```json
|
| 187 |
+
{
|
| 188 |
+
"task": {
|
| 189 |
+
"id": 42, "title": "Fix login bug", "status": "assigned",
|
| 190 |
+
"priority": "high", "assigned_to": "scout", "ticket_ref": "GEN-001",
|
| 191 |
+
"tags": ["bug"], "metadata": {}
|
| 192 |
+
}
|
| 193 |
+
}
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
### Skills — `/api/skills`
|
| 197 |
+
|
| 198 |
+
| Method | Min Role | Description |
|
| 199 |
+
|--------|----------|-------------|
|
| 200 |
+
| GET | viewer | List all skills across roots |
|
| 201 |
+
| GET `?mode=content&source=...&name=...` | viewer | Read a skill's SKILL.md content |
|
| 202 |
+
| GET `?mode=check&source=...&name=...` | viewer | Run security check on a skill |
|
| 203 |
+
| POST | operator | Create/upsert skill. Body: `{ source, name, content }` |
|
| 204 |
+
| PUT | operator | Update skill content. Body: `{ source, name, content }` |
|
| 205 |
+
| DELETE `?source=...&name=...` | operator | Delete a skill |
|
| 206 |
+
|
| 207 |
+
**Skill sources:** `user-agents`, `user-codex`, `project-agents`, `project-codex`, `openclaw`
|
| 208 |
+
|
| 209 |
+
### Status — `/api/status`
|
| 210 |
+
|
| 211 |
+
| Action | Min Role | Description |
|
| 212 |
+
|--------|----------|-------------|
|
| 213 |
+
| GET `?action=overview` | viewer | System status (uptime, memory, disk, sessions) |
|
| 214 |
+
| GET `?action=dashboard` | viewer | Aggregated dashboard data with DB stats |
|
| 215 |
+
| GET `?action=gateway` | viewer | Gateway process status and port check |
|
| 216 |
+
| GET `?action=models` | viewer | Available AI models (catalog + local Ollama) |
|
| 217 |
+
| GET `?action=health` | viewer | Health checks (gateway, disk, memory) |
|
| 218 |
+
| GET `?action=capabilities` | viewer | Feature flags: gateway reachable, Claude home, subscriptions |
|
| 219 |
+
|
| 220 |
+
### Adapters — `/api/adapters`
|
| 221 |
+
|
| 222 |
+
| Method | Min Role | Description |
|
| 223 |
+
|--------|----------|-------------|
|
| 224 |
+
| GET | viewer | List available framework adapter names |
|
| 225 |
+
| POST | operator | Execute adapter action (see Agent Lifecycle above) |
|
| 226 |
+
|
| 227 |
+
## Framework Adapter Protocol
|
| 228 |
+
|
| 229 |
+
All agent lifecycle operations use a single endpoint:
|
| 230 |
+
|
| 231 |
+
```
|
| 232 |
+
POST /api/adapters
|
| 233 |
+
Content-Type: application/json
|
| 234 |
+
x-api-key: <key>
|
| 235 |
+
|
| 236 |
+
{
|
| 237 |
+
"framework": "<adapter-name>",
|
| 238 |
+
"action": "<action>",
|
| 239 |
+
"payload": { ... }
|
| 240 |
+
}
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
**Available frameworks:** `generic`, `openclaw`, `crewai`, `langgraph`, `autogen`, `claude-sdk`
|
| 244 |
+
|
| 245 |
+
**Available actions:** `register`, `heartbeat`, `report`, `assignments`, `disconnect`
|
| 246 |
+
|
| 247 |
+
All adapters implement the same `FrameworkAdapter` interface — choose the one matching your agent framework, or use `generic` as a universal fallback.
|
| 248 |
+
|
| 249 |
+
**Payload shapes by action:**
|
| 250 |
+
|
| 251 |
+
| Action | Required Fields | Optional Fields |
|
| 252 |
+
|--------|----------------|-----------------|
|
| 253 |
+
| `register` | `agentId`, `name` | `metadata` |
|
| 254 |
+
| `heartbeat` | `agentId` | `status`, `metrics` |
|
| 255 |
+
| `report` | `taskId`, `agentId` | `progress`, `status`, `output` |
|
| 256 |
+
| `assignments` | `agentId` | — |
|
| 257 |
+
| `disconnect` | `agentId` | — |
|
| 258 |
+
|
| 259 |
+
## Environment Variables
|
| 260 |
+
|
| 261 |
+
| Variable | Default | Description |
|
| 262 |
+
|----------|---------|-------------|
|
| 263 |
+
| `API_KEY` | — | API key for agent/script authentication |
|
| 264 |
+
| `OPENCLAW_GATEWAY_HOST` | `127.0.0.1` | Gateway host address |
|
| 265 |
+
| `OPENCLAW_GATEWAY_PORT` | `18789` | Gateway port |
|
| 266 |
+
| `MISSION_CONTROL_DB_PATH` | `.data/mission-control.db` | SQLite database path |
|
| 267 |
+
| `OPENCLAW_STATE_DIR` | `~/.openclaw` | OpenClaw state directory |
|
| 268 |
+
| `OPENCLAW_CONFIG_PATH` | `<state-dir>/openclaw.json` | Gateway config file path |
|
| 269 |
+
| `MC_CLAUDE_HOME` | `~/.claude` | Claude home directory |
|
| 270 |
+
|
| 271 |
+
## Real-Time Events
|
| 272 |
+
|
| 273 |
+
MC broadcasts events via SSE (`/api/events`) and WebSocket. Key event types:
|
| 274 |
+
|
| 275 |
+
- `agent.created`, `agent.updated`, `agent.status_changed`
|
| 276 |
+
- `task.created`, `task.updated`, `task.status_changed`
|
| 277 |
+
|
| 278 |
+
Subscribe to SSE for live dashboard updates when building integrations.
|
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Production hardening overlay
|
| 2 |
+
# Usage: docker compose -f docker-compose.yml -f docker-compose.hardened.yml up -d
|
| 3 |
+
services:
|
| 4 |
+
mission-control:
|
| 5 |
+
logging:
|
| 6 |
+
driver: json-file
|
| 7 |
+
options:
|
| 8 |
+
max-size: "10m"
|
| 9 |
+
max-file: "3"
|
| 10 |
+
environment:
|
| 11 |
+
- MC_ALLOWED_HOSTS=localhost,127.0.0.1
|
| 12 |
+
- MC_COOKIE_SECURE=1
|
| 13 |
+
- MC_COOKIE_SAMESITE=strict
|
| 14 |
+
- MC_ENABLE_HSTS=1
|
| 15 |
+
networks:
|
| 16 |
+
mc-internal:
|
| 17 |
+
|
| 18 |
+
networks:
|
| 19 |
+
mc-internal:
|
| 20 |
+
driver: bridge
|
| 21 |
+
internal: true
|
|
@@ -11,7 +11,29 @@ services:
|
|
| 11 |
required: false
|
| 12 |
volumes:
|
| 13 |
- mc-data:/app/.data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
restart: unless-stopped
|
| 15 |
|
| 16 |
volumes:
|
| 17 |
mc-data:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
required: false
|
| 12 |
volumes:
|
| 13 |
- mc-data:/app/.data
|
| 14 |
+
read_only: true
|
| 15 |
+
tmpfs:
|
| 16 |
+
- /tmp
|
| 17 |
+
- /app/.next/cache
|
| 18 |
+
cap_drop:
|
| 19 |
+
- ALL
|
| 20 |
+
cap_add:
|
| 21 |
+
- NET_BIND_SERVICE
|
| 22 |
+
security_opt:
|
| 23 |
+
- no-new-privileges:true
|
| 24 |
+
pids_limit: 256
|
| 25 |
+
deploy:
|
| 26 |
+
resources:
|
| 27 |
+
limits:
|
| 28 |
+
memory: 512M
|
| 29 |
+
cpus: '1.0'
|
| 30 |
+
networks:
|
| 31 |
+
- mc-net
|
| 32 |
restart: unless-stopped
|
| 33 |
|
| 34 |
volumes:
|
| 35 |
mc-data:
|
| 36 |
+
|
| 37 |
+
networks:
|
| 38 |
+
mc-net:
|
| 39 |
+
driver: bridge
|
|
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Mission Control — Landing Page Handoff
|
| 2 |
+
|
| 3 |
+
> Last updated: 2026-03-07 | Version: 1.3.0 | Branch: `fix/refactor` (bb5029e)
|
| 4 |
+
|
| 5 |
+
This document contains all copy, stats, features, and structure needed to build or update the Mission Control landing page. Everything below reflects the current state of the shipped product.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Hero Section
|
| 10 |
+
|
| 11 |
+
**Headline:**
|
| 12 |
+
The Open-Source Dashboard for AI Agent Orchestration
|
| 13 |
+
|
| 14 |
+
**Subheadline:**
|
| 15 |
+
Manage agent fleets, track tasks, monitor costs, and orchestrate workflows — all from a single pane of glass. Zero external dependencies. One `pnpm start` to run.
|
| 16 |
+
|
| 17 |
+
**CTA:** `Get Started` -> GitHub repo | `Live Demo` -> demo instance (if available)
|
| 18 |
+
|
| 19 |
+
**Badges:**
|
| 20 |
+
- MIT License
|
| 21 |
+
- Next.js 16
|
| 22 |
+
- React 19
|
| 23 |
+
- TypeScript 5.7
|
| 24 |
+
- SQLite (WAL mode)
|
| 25 |
+
- 165 unit tests (Vitest)
|
| 26 |
+
- 295 E2E tests (Playwright)
|
| 27 |
+
|
| 28 |
+
**Hero image:** `docs/mission-control.jpg` (current dashboard screenshot — should be refreshed with latest UI)
|
| 29 |
+
|
| 30 |
+
---
|
| 31 |
+
|
| 32 |
+
## Key Stats (above the fold)
|
| 33 |
+
|
| 34 |
+
| Stat | Value |
|
| 35 |
+
|------|-------|
|
| 36 |
+
| Panels | 31 feature panels |
|
| 37 |
+
| API routes | 98 REST endpoints |
|
| 38 |
+
| Schema migrations | 36 |
|
| 39 |
+
| Test coverage | 165 unit + 295 E2E |
|
| 40 |
+
| Total commits | 239+ |
|
| 41 |
+
| External dependencies required | 0 (SQLite only, no Redis/Postgres/Docker) |
|
| 42 |
+
| Auth methods | 3 (session, API key, Google OAuth) |
|
| 43 |
+
| Framework adapters | 6 (OpenClaw, CrewAI, LangGraph, AutoGen, Claude SDK, Generic) |
|
| 44 |
+
|
| 45 |
+
---
|
| 46 |
+
|
| 47 |
+
## Feature Grid
|
| 48 |
+
|
| 49 |
+
### 1. Task Board (Kanban)
|
| 50 |
+
Six-column kanban (Inbox > Assigned > In Progress > Review > Quality Review > Done) with drag-and-drop, priority levels, assignments, threaded comments, and inline sub-agent spawning. Multi-project support with per-project ticket prefixes (e.g. `PA-001`).
|
| 51 |
+
|
| 52 |
+
### 2. Agent Management
|
| 53 |
+
Full lifecycle — register, heartbeat, wake, retire. Redesigned agent detail modal with compact overview, inline model selector, editable sub-agent configuration, and SOUL personality system. Local agent discovery from `~/.agents/`, `~/.codex/agents/`, `~/.claude/agents/`.
|
| 54 |
+
|
| 55 |
+
### 3. Real-Time Monitoring
|
| 56 |
+
Live activity feed, session inspector, and log viewer with filtering. WebSocket + SSE push updates with smart polling that pauses when you're away. Gateway connection state with live dot indicators.
|
| 57 |
+
|
| 58 |
+
### 4. Cost Tracking
|
| 59 |
+
Token usage dashboard with per-model breakdowns, trend charts, and cost analysis. Per-agent cost panels with session-level granularity.
|
| 60 |
+
|
| 61 |
+
### 5. Quality Gates (Aegis)
|
| 62 |
+
Built-in review system that blocks task completion without sign-off. Automated Aegis quality review — scheduler polls review tasks and approves/rejects based on configurable criteria.
|
| 63 |
+
|
| 64 |
+
### 6. Recurring Tasks
|
| 65 |
+
Natural language scheduling — "every morning at 9am", "every 2 hours". Zero-dependency schedule parser converts to cron. Template-clone pattern spawns dated child tasks (e.g. "Daily Report — Mar 07").
|
| 66 |
+
|
| 67 |
+
### 7. Task Dispatch
|
| 68 |
+
Scheduler polls assigned tasks and runs agents via CLI. Dispatched tasks link to agent sessions for full traceability.
|
| 69 |
+
|
| 70 |
+
### 8. Skills Hub
|
| 71 |
+
Browse, install, and manage agent skills from local directories and external registries (ClawdHub, skills.sh). Built-in security scanner checks for prompt injection, credential leaks, data exfiltration, and obfuscated content. Bidirectional disk-DB sync with SHA-256 change detection.
|
| 72 |
+
|
| 73 |
+
### 9. Claude Code Integration
|
| 74 |
+
- **Session tracking** — auto-discovers sessions from `~/.claude/projects/`, extracts tokens, model info, costs
|
| 75 |
+
- **Task bridge** — read-only integration surfaces Claude Code team tasks and configs
|
| 76 |
+
- **Direct CLI** — connect Claude Code, Codex, or any CLI directly without a gateway
|
| 77 |
+
|
| 78 |
+
### 10. Memory Knowledge Graph
|
| 79 |
+
Visual knowledge graph for agent memory in gateway mode. Interactive node-edge visualization of agent memory relationships.
|
| 80 |
+
|
| 81 |
+
### 11. Agent Messaging (Comms)
|
| 82 |
+
Session-threaded inter-agent communication via comms API (`a2a:*`, `coord:*`, `session:*`). Coordinator inbox support with runtime tool-call visibility.
|
| 83 |
+
|
| 84 |
+
### 12. Multi-Gateway
|
| 85 |
+
Connect to multiple agent gateways simultaneously. OS-level gateway discovery (systemd, Tailscale Serve). Auto-connect with health probes.
|
| 86 |
+
|
| 87 |
+
### 13. Framework Adapters
|
| 88 |
+
Built-in adapter layer for multi-agent registration: OpenClaw, CrewAI, LangGraph, AutoGen, Claude SDK, and generic fallback. Each normalizes registration, heartbeats, and task reporting.
|
| 89 |
+
|
| 90 |
+
### 14. Background Automation
|
| 91 |
+
Scheduled tasks for DB backups, stale record cleanup, agent heartbeat monitoring, recurring task spawning, and automated quality reviews.
|
| 92 |
+
|
| 93 |
+
### 15. Webhooks & Alerts
|
| 94 |
+
Outbound webhooks with delivery history, retry with exponential backoff, circuit breaker, and HMAC-SHA256 signature verification. Configurable alert rules with cooldowns.
|
| 95 |
+
|
| 96 |
+
### 16. GitHub Sync
|
| 97 |
+
Bidirectional GitHub Issues sync with label and assignee mapping. Full parity sync implementation.
|
| 98 |
+
|
| 99 |
+
### 17. Security
|
| 100 |
+
- Ed25519 device identity for gateway handshake
|
| 101 |
+
- scrypt password hashing
|
| 102 |
+
- RBAC (viewer, operator, admin)
|
| 103 |
+
- CSRF origin checks
|
| 104 |
+
- CSP headers
|
| 105 |
+
- Rate limiting with trusted proxy support
|
| 106 |
+
- Per-agent rate limiting with `x-agent-name` identity-based quotas
|
| 107 |
+
- Skill security scanner
|
| 108 |
+
|
| 109 |
+
### 18. Self-Update
|
| 110 |
+
GitHub release check with banner notification. One-click admin update (git pull, pnpm install, pnpm build). Dirty working trees rejected. All updates audit-logged.
|
| 111 |
+
|
| 112 |
+
### 19. Audit Trail
|
| 113 |
+
Complete action type coverage with grouped filters. Full audit history for compliance and debugging.
|
| 114 |
+
|
| 115 |
+
### 20. Pipelines & Workflows
|
| 116 |
+
Pipeline orchestration with workflow templates. Start, monitor, and manage multi-step agent workflows.
|
| 117 |
+
|
| 118 |
+
---
|
| 119 |
+
|
| 120 |
+
## "How It Works" Section
|
| 121 |
+
|
| 122 |
+
```
|
| 123 |
+
1. Clone & Start git clone ... && pnpm install && pnpm dev
|
| 124 |
+
2. Agents Register Via gateway, CLI, or self-registration endpoint
|
| 125 |
+
3. Tasks Flow Kanban board with automatic dispatch and quality gates
|
| 126 |
+
4. Monitor & Scale Real-time dashboards, cost tracking, recurring automation
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## Tech Stack Section
|
| 132 |
+
|
| 133 |
+
| Layer | Technology |
|
| 134 |
+
|-------|------------|
|
| 135 |
+
| Framework | Next.js 16 (App Router) |
|
| 136 |
+
| UI | React 19, Tailwind CSS 3.4 |
|
| 137 |
+
| Language | TypeScript 5.7 |
|
| 138 |
+
| Database | SQLite via better-sqlite3 (WAL mode) |
|
| 139 |
+
| State | Zustand 5 |
|
| 140 |
+
| Charts | Recharts 3 |
|
| 141 |
+
| Real-time | WebSocket + Server-Sent Events |
|
| 142 |
+
| Auth | scrypt hashing, session tokens, RBAC |
|
| 143 |
+
| Validation | Zod 4 |
|
| 144 |
+
| Testing | Vitest + Playwright |
|
| 145 |
+
|
| 146 |
+
---
|
| 147 |
+
|
| 148 |
+
## Auth & Access Section
|
| 149 |
+
|
| 150 |
+
**Three auth methods:**
|
| 151 |
+
1. Session cookie — username/password login (7-day expiry)
|
| 152 |
+
2. API key — `x-api-key` header for headless/agent access
|
| 153 |
+
3. Google Sign-In — OAuth with admin approval workflow
|
| 154 |
+
|
| 155 |
+
**Three roles:**
|
| 156 |
+
| Role | Access |
|
| 157 |
+
|------|--------|
|
| 158 |
+
| Viewer | Read-only dashboard access |
|
| 159 |
+
| Operator | Read + write (tasks, agents, chat, spawn) |
|
| 160 |
+
| Admin | Full access (users, settings, system ops, webhooks) |
|
| 161 |
+
|
| 162 |
+
---
|
| 163 |
+
|
| 164 |
+
## Architecture Diagram (simplified)
|
| 165 |
+
|
| 166 |
+
```
|
| 167 |
+
mission-control/
|
| 168 |
+
src/
|
| 169 |
+
app/api/ 98 REST API routes
|
| 170 |
+
components/
|
| 171 |
+
panels/ 31 feature panels
|
| 172 |
+
dashboard/ Overview dashboard
|
| 173 |
+
chat/ Agent chat workspace
|
| 174 |
+
layout/ NavRail, HeaderBar, LiveFeed
|
| 175 |
+
lib/
|
| 176 |
+
auth.ts Session + API key + Google OAuth
|
| 177 |
+
db.ts SQLite (WAL mode, 36 migrations)
|
| 178 |
+
scheduler.ts Background automation
|
| 179 |
+
websocket.ts Gateway WebSocket client
|
| 180 |
+
adapters/ 6 framework adapters
|
| 181 |
+
.data/ Runtime SQLite DB + token logs
|
| 182 |
+
```
|
| 183 |
+
|
| 184 |
+
---
|
| 185 |
+
|
| 186 |
+
## Quick Start Section
|
| 187 |
+
|
| 188 |
+
```bash
|
| 189 |
+
git clone https://github.com/builderz-labs/mission-control.git
|
| 190 |
+
cd mission-control
|
| 191 |
+
pnpm install
|
| 192 |
+
cp .env.example .env # edit with your values
|
| 193 |
+
pnpm dev # http://localhost:3000
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
Initial login seeded from `AUTH_USER` / `AUTH_PASS` on first run.
|
| 197 |
+
|
| 198 |
+
---
|
| 199 |
+
|
| 200 |
+
## Social Proof / Traction
|
| 201 |
+
|
| 202 |
+
- 239+ commits of active development
|
| 203 |
+
- Open-source MIT license
|
| 204 |
+
- Used in production for multi-agent orchestration
|
| 205 |
+
- Supports 6 agent frameworks out of the box
|
| 206 |
+
- Zero-config SQLite — no Docker, Redis, or Postgres required
|
| 207 |
+
|
| 208 |
+
---
|
| 209 |
+
|
| 210 |
+
## Roadmap / Coming Soon
|
| 211 |
+
|
| 212 |
+
- Agent-agnostic gateway support (OpenClaw, ZeroClaw, OpenFang, NeoBot, IronClaw, etc.)
|
| 213 |
+
- **Flight Deck** — native desktop companion app (Tauri v2) with real PTY terminal grid and system tray HUD (private beta)
|
| 214 |
+
- First-class per-agent cost breakdowns panel
|
| 215 |
+
- OAuth approval UI improvements
|
| 216 |
+
- API token rotation UI
|
| 217 |
+
|
| 218 |
+
---
|
| 219 |
+
|
| 220 |
+
## Recent Changelog (latest 20 notable changes)
|
| 221 |
+
|
| 222 |
+
1. **Memory knowledge graph** — interactive visualization for agent memory in gateway mode
|
| 223 |
+
2. **Agent detail modal redesign** — minimal header, compact overview, inline model selector
|
| 224 |
+
3. **Spawn/task unification** — spawn moved inline to task board, sub-agent config to agent detail
|
| 225 |
+
4. **Agent comms hardening** — session-threaded messaging with runtime tool visibility
|
| 226 |
+
5. **Audit trail** — complete action type coverage with grouped filters
|
| 227 |
+
6. **OS-level gateway discovery** — detect gateways via systemd and Tailscale Serve
|
| 228 |
+
7. **GitHub sync** — full parity sync with loading state fixes
|
| 229 |
+
8. **Automated Aegis quality review** — scheduler-driven approve/reject
|
| 230 |
+
9. **Task dispatch** — scheduler polls and runs agents via CLI with session linking
|
| 231 |
+
10. **Natural language recurring tasks** — zero-dep schedule parser + template spawning
|
| 232 |
+
11. **Claude Code task bridge** — read-only team task and config integration
|
| 233 |
+
12. **Agent card redesign** — gateway badge tooltips, ws:// localhost support
|
| 234 |
+
13. **Skills Hub** — registry integration, bidirectional sync, security scanner
|
| 235 |
+
14. **Per-agent rate limiting** — identity-based quotas via `x-agent-name`
|
| 236 |
+
15. **Agent self-registration** — autonomous onboarding endpoint
|
| 237 |
+
16. **Framework adapters** — OpenClaw, CrewAI, LangGraph, AutoGen, Claude SDK, generic
|
| 238 |
+
17. **Self-update mechanism** — one-click update with audit logging
|
| 239 |
+
18. **Local agent discovery** — auto-detect from ~/.agents, ~/.codex, ~/.claude
|
| 240 |
+
19. **Chat workspace** — embedded chat with local session continuation
|
| 241 |
+
20. **Ed25519 device identity** — secure gateway challenge-response handshake
|
| 242 |
+
|
| 243 |
+
---
|
| 244 |
+
|
| 245 |
+
## Footer
|
| 246 |
+
|
| 247 |
+
MIT License | 2026 Builderz Labs
|
| 248 |
+
GitHub: github.com/builderz-labs/mission-control
|
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Security Hardening Guide
|
| 2 |
+
|
| 3 |
+
Comprehensive security hardening guide for Mission Control and OpenClaw Gateway deployments.
|
| 4 |
+
|
| 5 |
+
## Quick Assessment
|
| 6 |
+
|
| 7 |
+
Run the automated security audit:
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
bash scripts/security-audit.sh # Check .env and configuration
|
| 11 |
+
bash scripts/station-doctor.sh # Check runtime health
|
| 12 |
+
```
|
| 13 |
+
|
| 14 |
+
Or use the diagnostics API (admin only):
|
| 15 |
+
|
| 16 |
+
```bash
|
| 17 |
+
curl -H "x-api-key: $API_KEY" http://localhost:3000/api/diagnostics
|
| 18 |
+
curl -H "x-api-key: $API_KEY" http://localhost:3000/api/security-audit?timeframe=day
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
The `posture.score` field (0-100) gives a quick posture assessment. The **Security Audit Panel** (`/security` in the dashboard) provides a full real-time view with timeline charts, agent trust scores, and eval results.
|
| 22 |
+
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+
## Mission Control Hardening
|
| 26 |
+
|
| 27 |
+
### 1. Credentials
|
| 28 |
+
|
| 29 |
+
**Generate strong credentials** using the included script:
|
| 30 |
+
|
| 31 |
+
```bash
|
| 32 |
+
bash scripts/generate-env.sh # Generates .env with random secrets
|
| 33 |
+
chmod 600 .env # Lock down permissions
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
The installer (`install.sh`) does this automatically. If you set up manually, ensure:
|
| 37 |
+
|
| 38 |
+
- `AUTH_PASS` is 12+ characters, not a dictionary word
|
| 39 |
+
- `API_KEY` is 32+ hex characters
|
| 40 |
+
- `AUTH_SECRET` is a unique random string
|
| 41 |
+
- `.env` file permissions are `600`
|
| 42 |
+
|
| 43 |
+
### 2. Network Access Control
|
| 44 |
+
|
| 45 |
+
Mission Control uses a host allowlist in production:
|
| 46 |
+
|
| 47 |
+
```env
|
| 48 |
+
# Only allow connections from these hosts (comma-separated)
|
| 49 |
+
MC_ALLOWED_HOSTS=localhost,127.0.0.1
|
| 50 |
+
|
| 51 |
+
# For Tailscale: MC_ALLOWED_HOSTS=localhost,127.0.0.1,100.*
|
| 52 |
+
# For a domain: MC_ALLOWED_HOSTS=mc.example.com,localhost
|
| 53 |
+
|
| 54 |
+
# NEVER set this in production:
|
| 55 |
+
# MC_ALLOW_ANY_HOST=1
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
Deploy behind a reverse proxy with TLS (Caddy, nginx, Tailscale Funnel) for any network-accessible deployment.
|
| 59 |
+
|
| 60 |
+
### 3. HTTPS & Cookies
|
| 61 |
+
|
| 62 |
+
For HTTPS deployments:
|
| 63 |
+
|
| 64 |
+
```env
|
| 65 |
+
MC_COOKIE_SECURE=1 # Cookies only sent over HTTPS
|
| 66 |
+
MC_COOKIE_SAMESITE=strict # CSRF protection
|
| 67 |
+
MC_ENABLE_HSTS=1 # HTTP Strict Transport Security
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
### 4. Rate Limiting
|
| 71 |
+
|
| 72 |
+
Rate limiting is enabled by default:
|
| 73 |
+
|
| 74 |
+
| Endpoint Type | Limit |
|
| 75 |
+
|--------------|-------|
|
| 76 |
+
| Login | 5 attempts/min (always active) |
|
| 77 |
+
| Mutations | 60 requests/min |
|
| 78 |
+
| Reads | 120 requests/min |
|
| 79 |
+
| Heavy operations | 10 requests/min |
|
| 80 |
+
| Agent heartbeat | 30/min per agent |
|
| 81 |
+
| Agent task polling | 20/min per agent |
|
| 82 |
+
|
| 83 |
+
Never set `MC_DISABLE_RATE_LIMIT=1` in production.
|
| 84 |
+
|
| 85 |
+
### 5. Docker Hardening
|
| 86 |
+
|
| 87 |
+
Use the production compose overlay:
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
docker compose -f docker-compose.yml -f docker-compose.hardened.yml up -d
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
This enables:
|
| 94 |
+
- **Read-only filesystem** with tmpfs for `/tmp` and `/app/.next/cache`
|
| 95 |
+
- **Capability dropping** — all Linux capabilities dropped, only `NET_BIND_SERVICE` retained
|
| 96 |
+
- **No new privileges** — prevents privilege escalation
|
| 97 |
+
- **PID limit** — prevents fork bombs
|
| 98 |
+
- **Memory/CPU limits** — prevents resource exhaustion
|
| 99 |
+
- **Log rotation** — prevents disk filling from verbose logging
|
| 100 |
+
- **HSTS, secure cookies** — forced via environment
|
| 101 |
+
|
| 102 |
+
### 6. Security Headers
|
| 103 |
+
|
| 104 |
+
Mission Control sets these headers automatically:
|
| 105 |
+
|
| 106 |
+
| Header | Value |
|
| 107 |
+
|--------|-------|
|
| 108 |
+
| `Content-Security-Policy` | `default-src 'self'; script-src 'self' 'unsafe-inline' 'nonce-...'` |
|
| 109 |
+
| `X-Frame-Options` | `DENY` |
|
| 110 |
+
| `X-Content-Type-Options` | `nosniff` |
|
| 111 |
+
| `Referrer-Policy` | `strict-origin-when-cross-origin` |
|
| 112 |
+
| `Permissions-Policy` | `camera=(), microphone=(), geolocation=()` |
|
| 113 |
+
| `X-Request-Id` | Unique per-request UUID for log correlation |
|
| 114 |
+
| `Strict-Transport-Security` | Set when `MC_ENABLE_HSTS=1` |
|
| 115 |
+
|
| 116 |
+
### 7. Audit Logging
|
| 117 |
+
|
| 118 |
+
All security-relevant events are logged to the audit trail:
|
| 119 |
+
|
| 120 |
+
- Login attempts (success and failure)
|
| 121 |
+
- Task mutations
|
| 122 |
+
- User management actions
|
| 123 |
+
- Settings changes
|
| 124 |
+
- Update operations
|
| 125 |
+
|
| 126 |
+
Additionally, the **security event system** automatically logs:
|
| 127 |
+
|
| 128 |
+
- Auth failures (invalid passwords, expired tokens, access denials)
|
| 129 |
+
- Rate limit hits (429 responses with IP/agent correlation)
|
| 130 |
+
- Injection attempts (prompt injection, command injection, exfiltration)
|
| 131 |
+
- Secret exposures (AWS keys, GitHub tokens, Stripe keys, JWTs, private keys detected in agent messages)
|
| 132 |
+
- MCP tool calls (agent, tool, duration, success/failure)
|
| 133 |
+
|
| 134 |
+
These events feed into the **Security Audit Panel** (`/security`) which provides:
|
| 135 |
+
|
| 136 |
+
- **Posture score** (0-100) with level badges (hardened/secure/needs-attention/at-risk)
|
| 137 |
+
- **Agent trust scores** — weighted calculation based on auth failures, injection attempts, and task success rates
|
| 138 |
+
- **MCP call audit** — tool-use frequency, success/failure rates per agent
|
| 139 |
+
- **Timeline visualization** — event density over selected timeframe
|
| 140 |
+
|
| 141 |
+
Configure retention: `MC_RETAIN_AUDIT_DAYS=365` (default: 1 year).
|
| 142 |
+
|
| 143 |
+
### 8. Hook Profiles
|
| 144 |
+
|
| 145 |
+
Security strictness is tunable via hook profiles in Settings > Security Profiles:
|
| 146 |
+
|
| 147 |
+
| Profile | Secret Scanning | MCP Auditing | Block on Secrets | Rate Limit Multiplier |
|
| 148 |
+
|---------|----------------|--------------|------------------|----------------------|
|
| 149 |
+
| **minimal** | Off | Off | No | 2x (relaxed) |
|
| 150 |
+
| **standard** (default) | On | On | No | 1x |
|
| 151 |
+
| **strict** | On | On | Yes (blocks messages) | 0.5x (tighter) |
|
| 152 |
+
|
| 153 |
+
Set via the Settings panel or the `hook_profile` key in the settings API.
|
| 154 |
+
|
| 155 |
+
### 9. Agent Eval Framework
|
| 156 |
+
|
| 157 |
+
The four-layer eval stack helps detect degrading agent quality:
|
| 158 |
+
|
| 159 |
+
- **Output evals** — score task completion against golden datasets
|
| 160 |
+
- **Trace evals** — convergence scoring (>3.0 indicates looping behavior)
|
| 161 |
+
- **Component evals** — tool reliability from MCP call logs (p50/p95/p99 latency)
|
| 162 |
+
- **Drift detection** — 10% threshold vs 4-week rolling baseline triggers alerts
|
| 163 |
+
|
| 164 |
+
Access via `/api/agents/evals` or the Security Audit Panel's eval section.
|
| 165 |
+
|
| 166 |
+
### 10. Data Retention
|
| 167 |
+
|
| 168 |
+
```env
|
| 169 |
+
MC_RETAIN_ACTIVITIES_DAYS=90 # Activity feed
|
| 170 |
+
MC_RETAIN_AUDIT_DAYS=365 # Security audit trail
|
| 171 |
+
MC_RETAIN_LOGS_DAYS=30 # Application logs
|
| 172 |
+
MC_RETAIN_NOTIFICATIONS_DAYS=60 # Notifications
|
| 173 |
+
MC_RETAIN_PIPELINE_RUNS_DAYS=90 # Pipeline logs
|
| 174 |
+
MC_RETAIN_TOKEN_USAGE_DAYS=90 # Token/cost records
|
| 175 |
+
MC_RETAIN_GATEWAY_SESSIONS_DAYS=90 # Gateway session history
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
---
|
| 179 |
+
|
| 180 |
+
## OpenClaw Gateway Hardening
|
| 181 |
+
|
| 182 |
+
Mission Control acts as the mothership for your OpenClaw fleet. The installer automatically checks and repairs common OpenClaw configuration issues.
|
| 183 |
+
|
| 184 |
+
### 1. Network Security
|
| 185 |
+
|
| 186 |
+
- **Never expose the gateway publicly.** It runs on port 18789 by default.
|
| 187 |
+
- **Bind to localhost:** Set `gateway.bind: "loopback"` in `openclaw.json`.
|
| 188 |
+
- **Use SSH tunneling or Tailscale** for remote access.
|
| 189 |
+
- **Docker users:** Be aware that Docker can bypass UFW rules. Use `DOCKER-USER` chain rules.
|
| 190 |
+
|
| 191 |
+
### 2. Authentication
|
| 192 |
+
|
| 193 |
+
- **Always enable gateway auth** with a strong random token.
|
| 194 |
+
- Generate: `openclaw doctor --generate-gateway-token`
|
| 195 |
+
- Store in `OPENCLAW_GATEWAY_TOKEN` env var (never in `NEXT_PUBLIC_*` variables).
|
| 196 |
+
- Rotate regularly.
|
| 197 |
+
|
| 198 |
+
### 3. Hardened Gateway Configuration
|
| 199 |
+
|
| 200 |
+
```json
|
| 201 |
+
{
|
| 202 |
+
"gateway": {
|
| 203 |
+
"mode": "local",
|
| 204 |
+
"bind": "loopback",
|
| 205 |
+
"auth": {
|
| 206 |
+
"mode": "token",
|
| 207 |
+
"token": "replace-with-long-random-token"
|
| 208 |
+
}
|
| 209 |
+
},
|
| 210 |
+
"session": {
|
| 211 |
+
"dmScope": "per-channel-peer"
|
| 212 |
+
},
|
| 213 |
+
"tools": {
|
| 214 |
+
"profile": "messaging",
|
| 215 |
+
"deny": ["group:automation", "group:runtime", "group:fs", "sessions_spawn", "sessions_send"],
|
| 216 |
+
"fs": { "workspaceOnly": true },
|
| 217 |
+
"exec": { "security": "deny", "ask": "always" }
|
| 218 |
+
},
|
| 219 |
+
"elevated": { "enabled": false }
|
| 220 |
+
}
|
| 221 |
+
```
|
| 222 |
+
|
| 223 |
+
### 4. File Permissions
|
| 224 |
+
|
| 225 |
+
```bash
|
| 226 |
+
chmod 700 ~/.openclaw
|
| 227 |
+
chmod 600 ~/.openclaw/openclaw.json
|
| 228 |
+
chmod 600 ~/.openclaw/credentials/*
|
| 229 |
+
```
|
| 230 |
+
|
| 231 |
+
### 5. Tool Security
|
| 232 |
+
|
| 233 |
+
- Apply the principle of least privilege — only grant tools the agent needs.
|
| 234 |
+
- Audit third-party skills before installing (Mission Control's Skills Hub runs automatic security scans).
|
| 235 |
+
- Run agents processing untrusted content in a sandbox with a minimal toolset.
|
| 236 |
+
|
| 237 |
+
### 6. Monitoring
|
| 238 |
+
|
| 239 |
+
- Enable comprehensive logging: `logging.redactSensitive: "tools"`
|
| 240 |
+
- Store logs separately where agents cannot modify them.
|
| 241 |
+
- Use Mission Control's diagnostics API to monitor gateway health.
|
| 242 |
+
- Have an incident response plan: stop gateway, revoke API keys, review audit logs.
|
| 243 |
+
|
| 244 |
+
### 7. Known CVEs
|
| 245 |
+
|
| 246 |
+
Keep OpenClaw updated. Notable past vulnerabilities:
|
| 247 |
+
|
| 248 |
+
| CVE | Severity | Description | Fixed In |
|
| 249 |
+
|-----|----------|-------------|----------|
|
| 250 |
+
| CVE-2026-25253 | Critical | RCE via Control UI token hijack | v2026.1.29 |
|
| 251 |
+
| CVE-2026-26327 | High | Auth bypass via gateway spoofing | v2026.2.25 |
|
| 252 |
+
| CVE-2026-26322 | High | SSRF | v2026.2.25 |
|
| 253 |
+
| CVE-2026-26329 | High | Path traversal | v2026.2.25 |
|
| 254 |
+
| CVE-2026-26319 | Medium | Missing webhook auth | v2026.2.25 |
|
| 255 |
+
|
| 256 |
+
---
|
| 257 |
+
|
| 258 |
+
## Deployment Architecture
|
| 259 |
+
|
| 260 |
+
For production, the recommended architecture is:
|
| 261 |
+
|
| 262 |
+
```
|
| 263 |
+
Internet
|
| 264 |
+
|
|
| 265 |
+
[Reverse Proxy (Caddy/nginx) + TLS]
|
| 266 |
+
|
|
| 267 |
+
[Mission Control :3000] ---- [SQLite .data/]
|
| 268 |
+
|
|
| 269 |
+
[OpenClaw Gateway :18789 (localhost only)]
|
| 270 |
+
|
|
| 271 |
+
[Agent Workspaces]
|
| 272 |
+
```
|
| 273 |
+
|
| 274 |
+
- Reverse proxy handles TLS termination, rate limiting, and access control
|
| 275 |
+
- Mission Control listens on localhost or a private network
|
| 276 |
+
- OpenClaw Gateway is bound to loopback only
|
| 277 |
+
- Agent workspaces are isolated per-agent directories
|
|
@@ -48,6 +48,32 @@ PORT=3000 pnpm start
|
|
| 48 |
|
| 49 |
**Important:** The production build bundles platform-specific native binaries. You must run `pnpm install` and `pnpm build` on the same OS and architecture as the target server. A build created on macOS will not work on Linux.
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
## Production (Docker)
|
| 52 |
|
| 53 |
```bash
|
|
|
|
| 48 |
|
| 49 |
**Important:** The production build bundles platform-specific native binaries. You must run `pnpm install` and `pnpm build` on the same OS and architecture as the target server. A build created on macOS will not work on Linux.
|
| 50 |
|
| 51 |
+
## Production (Standalone)
|
| 52 |
+
|
| 53 |
+
Use this for bare-metal deployments that run Next's standalone server directly.
|
| 54 |
+
This path is preferred over ad hoc `node .next/standalone/server.js` because it
|
| 55 |
+
syncs `.next/static` and `public/` into the standalone bundle before launch.
|
| 56 |
+
|
| 57 |
+
```bash
|
| 58 |
+
pnpm install --frozen-lockfile
|
| 59 |
+
pnpm build
|
| 60 |
+
pnpm start:standalone
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
For a full in-place update on the target host:
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
BRANCH=fix/refactor PORT=3000 pnpm deploy:standalone
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
What `deploy:standalone` does:
|
| 70 |
+
- fetches and fast-forwards the requested branch
|
| 71 |
+
- reinstalls dependencies with the lockfile
|
| 72 |
+
- rebuilds from a clean `.next/`
|
| 73 |
+
- stops the old process bound to the target port
|
| 74 |
+
- starts the standalone server through `scripts/start-standalone.sh`
|
| 75 |
+
- verifies that the rendered login page references a CSS asset and that the CSS is served as `text/css`
|
| 76 |
+
|
| 77 |
## Production (Docker)
|
| 78 |
|
| 79 |
```bash
|
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Onboarding + Walkthrough hardening plan
|
| 2 |
+
|
| 3 |
+
Base branch: `fix/refactor`
|
| 4 |
+
Working branch: `fix/refactor-onboarding-walkthrough`
|
| 5 |
+
|
| 6 |
+
## Goals
|
| 7 |
+
- Verify current onboarding and walkthrough flows are functional.
|
| 8 |
+
- Fix edge cases for first-run, skip, replay, and recovery states.
|
| 9 |
+
- Improve UX discoverability of walkthrough entry points.
|
| 10 |
+
- Add regression tests to keep flows stable.
|
| 11 |
+
|
| 12 |
+
## Phase 1: audit and test map
|
| 13 |
+
1. Identify current onboarding/walkthrough code paths.
|
| 14 |
+
2. Document triggers, persistence flags, and routing.
|
| 15 |
+
3. Add failing tests for first-run, skip, replay, and already-seen states.
|
| 16 |
+
|
| 17 |
+
## Phase 2: implementation hardening
|
| 18 |
+
1. Fix state transitions and persistence updates.
|
| 19 |
+
2. Ensure walkthrough can be reopened from primary UI.
|
| 20 |
+
3. Add visible hint/help entry to improve discoverability.
|
| 21 |
+
4. Handle corrupted or partial onboarding state safely.
|
| 22 |
+
|
| 23 |
+
## Phase 3: verification
|
| 24 |
+
1. Run targeted tests for onboarding/walkthrough.
|
| 25 |
+
2. Run full project checks.
|
| 26 |
+
3. Validate end-to-end flow manually in local dev.
|
| 27 |
+
|
| 28 |
+
## Deliverables
|
| 29 |
+
- Code changes in onboarding/walkthrough modules
|
| 30 |
+
- Automated tests covering key onboarding paths
|
| 31 |
+
- Updated docs/help text for walkthrough discoverability
|
|
@@ -0,0 +1,429 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Mission Control — One-Command Installer
|
| 3 |
+
# The mothership for your OpenClaw fleet.
|
| 4 |
+
#
|
| 5 |
+
# Usage:
|
| 6 |
+
# curl -fsSL https://raw.githubusercontent.com/builderz-labs/mission-control/main/install.sh | bash
|
| 7 |
+
# # or
|
| 8 |
+
# bash install.sh [--docker|--local] [--port PORT] [--data-dir DIR]
|
| 9 |
+
#
|
| 10 |
+
# Installs Mission Control and optionally repairs/configures OpenClaw.
|
| 11 |
+
|
| 12 |
+
set -euo pipefail
|
| 13 |
+
|
| 14 |
+
# ── Defaults ──────────────────────────────────────────────────────────────────
|
| 15 |
+
MC_PORT="${MC_PORT:-3000}"
|
| 16 |
+
MC_DATA_DIR=""
|
| 17 |
+
DEPLOY_MODE=""
|
| 18 |
+
SKIP_OPENCLAW=false
|
| 19 |
+
REPO_URL="https://github.com/builderz-labs/mission-control.git"
|
| 20 |
+
INSTALL_DIR="${MC_INSTALL_DIR:-$(pwd)/mission-control}"
|
| 21 |
+
|
| 22 |
+
# ── Parse arguments ───────────────────────────────────────────────────────────
|
| 23 |
+
while [[ $# -gt 0 ]]; do
|
| 24 |
+
case "$1" in
|
| 25 |
+
--docker) DEPLOY_MODE="docker"; shift ;;
|
| 26 |
+
--local) DEPLOY_MODE="local"; shift ;;
|
| 27 |
+
--port) MC_PORT="$2"; shift 2 ;;
|
| 28 |
+
--data-dir) MC_DATA_DIR="$2"; shift 2 ;;
|
| 29 |
+
--skip-openclaw) SKIP_OPENCLAW=true; shift ;;
|
| 30 |
+
--dir) INSTALL_DIR="$2"; shift 2 ;;
|
| 31 |
+
-h|--help)
|
| 32 |
+
echo "Usage: install.sh [--docker|--local] [--port PORT] [--data-dir DIR] [--dir INSTALL_DIR] [--skip-openclaw]"
|
| 33 |
+
exit 0 ;;
|
| 34 |
+
*) echo "Unknown option: $1"; exit 1 ;;
|
| 35 |
+
esac
|
| 36 |
+
done
|
| 37 |
+
|
| 38 |
+
# ── Helpers ───────────────────────────────────────────────────────────────────
|
| 39 |
+
info() { echo -e "\033[1;34m[MC]\033[0m $*"; }
|
| 40 |
+
ok() { echo -e "\033[1;32m[OK]\033[0m $*"; }
|
| 41 |
+
warn() { echo -e "\033[1;33m[!!]\033[0m $*"; }
|
| 42 |
+
err() { echo -e "\033[1;31m[ERR]\033[0m $*" >&2; }
|
| 43 |
+
die() { err "$*"; exit 1; }
|
| 44 |
+
|
| 45 |
+
command_exists() { command -v "$1" &>/dev/null; }
|
| 46 |
+
|
| 47 |
+
detect_os() {
|
| 48 |
+
local os arch
|
| 49 |
+
os="$(uname -s)"
|
| 50 |
+
arch="$(uname -m)"
|
| 51 |
+
|
| 52 |
+
case "$os" in
|
| 53 |
+
Linux) OS="linux" ;;
|
| 54 |
+
Darwin) OS="darwin" ;;
|
| 55 |
+
*) die "Unsupported OS: $os" ;;
|
| 56 |
+
esac
|
| 57 |
+
|
| 58 |
+
case "$arch" in
|
| 59 |
+
x86_64|amd64) ARCH="x64" ;;
|
| 60 |
+
aarch64|arm64) ARCH="arm64" ;;
|
| 61 |
+
*) die "Unsupported architecture: $arch" ;;
|
| 62 |
+
esac
|
| 63 |
+
|
| 64 |
+
ok "Detected $OS/$ARCH"
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
check_prerequisites() {
|
| 68 |
+
local has_docker=false has_node=false
|
| 69 |
+
|
| 70 |
+
if command_exists docker && docker info &>/dev/null 2>&1; then
|
| 71 |
+
has_docker=true
|
| 72 |
+
ok "Docker available ($(docker --version | head -1))"
|
| 73 |
+
fi
|
| 74 |
+
|
| 75 |
+
if command_exists node; then
|
| 76 |
+
local node_major
|
| 77 |
+
node_major=$(node -v | sed 's/v//' | cut -d. -f1)
|
| 78 |
+
if [[ "$node_major" -ge 20 ]]; then
|
| 79 |
+
has_node=true
|
| 80 |
+
ok "Node.js $(node -v) available"
|
| 81 |
+
else
|
| 82 |
+
warn "Node.js $(node -v) found but v20+ required"
|
| 83 |
+
fi
|
| 84 |
+
fi
|
| 85 |
+
|
| 86 |
+
if ! $has_docker && ! $has_node; then
|
| 87 |
+
die "Either Docker or Node.js 20+ is required. Install one and retry."
|
| 88 |
+
fi
|
| 89 |
+
|
| 90 |
+
# Auto-select deploy mode if not specified
|
| 91 |
+
if [[ -z "$DEPLOY_MODE" ]]; then
|
| 92 |
+
if $has_docker; then
|
| 93 |
+
DEPLOY_MODE="docker"
|
| 94 |
+
info "Auto-selected Docker deployment (use --local to override)"
|
| 95 |
+
else
|
| 96 |
+
DEPLOY_MODE="local"
|
| 97 |
+
info "Auto-selected local deployment (Docker not available)"
|
| 98 |
+
fi
|
| 99 |
+
fi
|
| 100 |
+
|
| 101 |
+
# Validate chosen mode
|
| 102 |
+
if [[ "$DEPLOY_MODE" == "docker" ]] && ! $has_docker; then
|
| 103 |
+
die "Docker deployment requested but Docker is not available"
|
| 104 |
+
fi
|
| 105 |
+
if [[ "$DEPLOY_MODE" == "local" ]] && ! $has_node; then
|
| 106 |
+
die "Local deployment requested but Node.js 20+ is not available"
|
| 107 |
+
fi
|
| 108 |
+
if [[ "$DEPLOY_MODE" == "local" ]] && ! command_exists pnpm; then
|
| 109 |
+
info "Installing pnpm via corepack..."
|
| 110 |
+
corepack enable && corepack prepare pnpm@latest --activate
|
| 111 |
+
ok "pnpm installed"
|
| 112 |
+
fi
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
# ── Clone or update repo ─────────────────────────────────────────────────────
|
| 116 |
+
fetch_source() {
|
| 117 |
+
if [[ -d "$INSTALL_DIR/.git" ]]; then
|
| 118 |
+
info "Updating existing installation at $INSTALL_DIR..."
|
| 119 |
+
cd "$INSTALL_DIR"
|
| 120 |
+
git fetch --tags
|
| 121 |
+
local latest_tag
|
| 122 |
+
latest_tag=$(git describe --tags --abbrev=0 origin/main 2>/dev/null || echo "")
|
| 123 |
+
if [[ -n "$latest_tag" ]]; then
|
| 124 |
+
git checkout "$latest_tag"
|
| 125 |
+
ok "Checked out $latest_tag"
|
| 126 |
+
else
|
| 127 |
+
git pull origin main
|
| 128 |
+
ok "Updated to latest main"
|
| 129 |
+
fi
|
| 130 |
+
else
|
| 131 |
+
info "Cloning Mission Control..."
|
| 132 |
+
if command_exists git; then
|
| 133 |
+
git clone --depth 1 "$REPO_URL" "$INSTALL_DIR"
|
| 134 |
+
cd "$INSTALL_DIR"
|
| 135 |
+
ok "Cloned to $INSTALL_DIR"
|
| 136 |
+
else
|
| 137 |
+
die "git is required to clone the repository"
|
| 138 |
+
fi
|
| 139 |
+
fi
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
# ── Generate .env ─────────────────────────────────────────────────────────────
|
| 143 |
+
setup_env() {
|
| 144 |
+
if [[ -f "$INSTALL_DIR/.env" ]]; then
|
| 145 |
+
info "Existing .env found — keeping current configuration"
|
| 146 |
+
return
|
| 147 |
+
fi
|
| 148 |
+
|
| 149 |
+
info "Generating secure .env configuration..."
|
| 150 |
+
bash "$INSTALL_DIR/scripts/generate-env.sh" "$INSTALL_DIR/.env"
|
| 151 |
+
|
| 152 |
+
# Set the port if non-default
|
| 153 |
+
if [[ "$MC_PORT" != "3000" ]]; then
|
| 154 |
+
if [[ "$(uname)" == "Darwin" ]]; then
|
| 155 |
+
sed -i '' "s|^# PORT=3000|PORT=$MC_PORT|" "$INSTALL_DIR/.env"
|
| 156 |
+
else
|
| 157 |
+
sed -i "s|^# PORT=3000|PORT=$MC_PORT|" "$INSTALL_DIR/.env"
|
| 158 |
+
fi
|
| 159 |
+
fi
|
| 160 |
+
|
| 161 |
+
ok "Secure .env generated"
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
# ── Docker deployment ─────────────────────────────────────────────────────────
|
| 165 |
+
deploy_docker() {
|
| 166 |
+
info "Starting Docker deployment..."
|
| 167 |
+
|
| 168 |
+
export MC_PORT
|
| 169 |
+
docker compose up -d --build
|
| 170 |
+
|
| 171 |
+
# Wait for healthy
|
| 172 |
+
info "Waiting for Mission Control to become healthy..."
|
| 173 |
+
local retries=30
|
| 174 |
+
while [[ $retries -gt 0 ]]; do
|
| 175 |
+
if docker compose ps --format json 2>/dev/null | grep -q '"Health":"healthy"'; then
|
| 176 |
+
break
|
| 177 |
+
fi
|
| 178 |
+
# Fallback: try HTTP check
|
| 179 |
+
if curl -sf "http://localhost:$MC_PORT/login" &>/dev/null; then
|
| 180 |
+
break
|
| 181 |
+
fi
|
| 182 |
+
sleep 2
|
| 183 |
+
((retries--))
|
| 184 |
+
done
|
| 185 |
+
|
| 186 |
+
if [[ $retries -eq 0 ]]; then
|
| 187 |
+
warn "Timeout waiting for health check — container may still be starting"
|
| 188 |
+
docker compose logs --tail 20
|
| 189 |
+
else
|
| 190 |
+
ok "Mission Control is running in Docker"
|
| 191 |
+
fi
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
# ── Local deployment ──────────────────────────────────────────────────────────
|
| 195 |
+
deploy_local() {
|
| 196 |
+
info "Starting local deployment..."
|
| 197 |
+
|
| 198 |
+
cd "$INSTALL_DIR"
|
| 199 |
+
pnpm install --frozen-lockfile 2>/dev/null || pnpm install
|
| 200 |
+
ok "Dependencies installed"
|
| 201 |
+
|
| 202 |
+
info "Building Mission Control..."
|
| 203 |
+
pnpm build
|
| 204 |
+
ok "Build complete"
|
| 205 |
+
|
| 206 |
+
# Create systemd service on Linux if systemctl is available
|
| 207 |
+
if [[ "$OS" == "linux" ]] && command_exists systemctl; then
|
| 208 |
+
setup_systemd
|
| 209 |
+
fi
|
| 210 |
+
|
| 211 |
+
info "Starting Mission Control..."
|
| 212 |
+
PORT="$MC_PORT" nohup pnpm start > "$INSTALL_DIR/.data/mc.log" 2>&1 &
|
| 213 |
+
local pid=$!
|
| 214 |
+
echo "$pid" > "$INSTALL_DIR/.data/mc.pid"
|
| 215 |
+
|
| 216 |
+
sleep 3
|
| 217 |
+
if kill -0 "$pid" 2>/dev/null; then
|
| 218 |
+
ok "Mission Control running (PID $pid)"
|
| 219 |
+
else
|
| 220 |
+
err "Failed to start. Check logs: $INSTALL_DIR/.data/mc.log"
|
| 221 |
+
exit 1
|
| 222 |
+
fi
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
# ── Systemd service ──────────────────────────────────────────────────────────
|
| 226 |
+
setup_systemd() {
|
| 227 |
+
local service_file="/etc/systemd/system/mission-control.service"
|
| 228 |
+
if [[ -f "$service_file" ]]; then
|
| 229 |
+
info "Systemd service already exists"
|
| 230 |
+
return
|
| 231 |
+
fi
|
| 232 |
+
|
| 233 |
+
info "Creating systemd service..."
|
| 234 |
+
local user
|
| 235 |
+
user="$(whoami)"
|
| 236 |
+
local node_path
|
| 237 |
+
node_path="$(which node)"
|
| 238 |
+
|
| 239 |
+
cat > /tmp/mission-control.service <<UNIT
|
| 240 |
+
[Unit]
|
| 241 |
+
Description=Mission Control - OpenClaw Agent Dashboard
|
| 242 |
+
After=network.target
|
| 243 |
+
|
| 244 |
+
[Service]
|
| 245 |
+
Type=simple
|
| 246 |
+
User=$user
|
| 247 |
+
WorkingDirectory=$INSTALL_DIR
|
| 248 |
+
ExecStart=$node_path $INSTALL_DIR/.next/standalone/server.js
|
| 249 |
+
Restart=on-failure
|
| 250 |
+
RestartSec=5
|
| 251 |
+
Environment=NODE_ENV=production
|
| 252 |
+
Environment=PORT=$MC_PORT
|
| 253 |
+
EnvironmentFile=$INSTALL_DIR/.env
|
| 254 |
+
|
| 255 |
+
[Install]
|
| 256 |
+
WantedBy=multi-user.target
|
| 257 |
+
UNIT
|
| 258 |
+
|
| 259 |
+
if [[ "$(id -u)" -eq 0 ]]; then
|
| 260 |
+
mv /tmp/mission-control.service "$service_file"
|
| 261 |
+
systemctl daemon-reload
|
| 262 |
+
systemctl enable mission-control
|
| 263 |
+
ok "Systemd service installed and enabled"
|
| 264 |
+
else
|
| 265 |
+
info "Run as root to install systemd service:"
|
| 266 |
+
info " sudo mv /tmp/mission-control.service $service_file"
|
| 267 |
+
info " sudo systemctl daemon-reload && sudo systemctl enable --now mission-control"
|
| 268 |
+
fi
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
# ── OpenClaw fleet check ─────────────────────────────────────────────────────
|
| 272 |
+
check_openclaw() {
|
| 273 |
+
if $SKIP_OPENCLAW; then
|
| 274 |
+
info "Skipping OpenClaw checks (--skip-openclaw)"
|
| 275 |
+
return
|
| 276 |
+
fi
|
| 277 |
+
|
| 278 |
+
echo ""
|
| 279 |
+
info "=== OpenClaw Fleet Check ==="
|
| 280 |
+
|
| 281 |
+
# Check if openclaw binary exists
|
| 282 |
+
if command_exists openclaw; then
|
| 283 |
+
local oc_version
|
| 284 |
+
oc_version="$(openclaw --version 2>/dev/null || echo 'unknown')"
|
| 285 |
+
ok "OpenClaw binary found: $oc_version"
|
| 286 |
+
elif command_exists clawdbot; then
|
| 287 |
+
local cb_version
|
| 288 |
+
cb_version="$(clawdbot --version 2>/dev/null || echo 'unknown')"
|
| 289 |
+
ok "ClawdBot binary found: $cb_version (legacy)"
|
| 290 |
+
warn "Consider upgrading to openclaw CLI"
|
| 291 |
+
else
|
| 292 |
+
info "OpenClaw CLI not found — install it to enable agent orchestration"
|
| 293 |
+
info " See: https://github.com/builderz-labs/openclaw"
|
| 294 |
+
return
|
| 295 |
+
fi
|
| 296 |
+
|
| 297 |
+
# Check OpenClaw home directory
|
| 298 |
+
local oc_home="${OPENCLAW_HOME:-$HOME/.openclaw}"
|
| 299 |
+
if [[ -d "$oc_home" ]]; then
|
| 300 |
+
ok "OpenClaw home: $oc_home"
|
| 301 |
+
|
| 302 |
+
# Check config
|
| 303 |
+
local oc_config="$oc_home/openclaw.json"
|
| 304 |
+
if [[ -f "$oc_config" ]]; then
|
| 305 |
+
ok "Config found: $oc_config"
|
| 306 |
+
else
|
| 307 |
+
warn "No openclaw.json found at $oc_config"
|
| 308 |
+
info "Mission Control will create a default config on first gateway connection"
|
| 309 |
+
fi
|
| 310 |
+
|
| 311 |
+
# Check for stale PID files
|
| 312 |
+
local stale_count=0
|
| 313 |
+
for pidfile in "$oc_home"/*.pid "$oc_home"/pids/*.pid; do
|
| 314 |
+
[[ -f "$pidfile" ]] || continue
|
| 315 |
+
local pid
|
| 316 |
+
pid="$(cat "$pidfile" 2>/dev/null)" || continue
|
| 317 |
+
if ! kill -0 "$pid" 2>/dev/null; then
|
| 318 |
+
rm -f "$pidfile"
|
| 319 |
+
((stale_count++))
|
| 320 |
+
fi
|
| 321 |
+
done
|
| 322 |
+
if [[ $stale_count -gt 0 ]]; then
|
| 323 |
+
ok "Cleaned $stale_count stale PID file(s)"
|
| 324 |
+
fi
|
| 325 |
+
|
| 326 |
+
# Check logs directory size
|
| 327 |
+
local logs_dir="$oc_home/logs"
|
| 328 |
+
if [[ -d "$logs_dir" ]]; then
|
| 329 |
+
local logs_size
|
| 330 |
+
if [[ "$(uname)" == "Darwin" ]]; then
|
| 331 |
+
logs_size="$(du -sh "$logs_dir" 2>/dev/null | cut -f1)"
|
| 332 |
+
else
|
| 333 |
+
logs_size="$(du -sh "$logs_dir" 2>/dev/null | cut -f1)"
|
| 334 |
+
fi
|
| 335 |
+
info "Logs directory: $logs_size ($logs_dir)"
|
| 336 |
+
|
| 337 |
+
# Clean old logs (> 30 days)
|
| 338 |
+
local old_logs
|
| 339 |
+
old_logs=$(find "$logs_dir" -name "*.log" -mtime +30 2>/dev/null | wc -l | tr -d ' ')
|
| 340 |
+
if [[ "$old_logs" -gt 0 ]]; then
|
| 341 |
+
find "$logs_dir" -name "*.log" -mtime +30 -delete 2>/dev/null || true
|
| 342 |
+
ok "Cleaned $old_logs log file(s) older than 30 days"
|
| 343 |
+
fi
|
| 344 |
+
fi
|
| 345 |
+
|
| 346 |
+
# Check workspace directory
|
| 347 |
+
local workspace="$oc_home/workspace"
|
| 348 |
+
if [[ -d "$workspace" ]]; then
|
| 349 |
+
local agent_count
|
| 350 |
+
agent_count=$(find "$workspace" -maxdepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')
|
| 351 |
+
((agent_count--)) # subtract the workspace dir itself
|
| 352 |
+
info "Workspace: $agent_count agent workspace(s) in $workspace"
|
| 353 |
+
fi
|
| 354 |
+
else
|
| 355 |
+
info "OpenClaw home not found at $oc_home"
|
| 356 |
+
info "Set OPENCLAW_HOME in .env to point to your OpenClaw state directory"
|
| 357 |
+
fi
|
| 358 |
+
|
| 359 |
+
# Check gateway port
|
| 360 |
+
local gw_host="${OPENCLAW_GATEWAY_HOST:-127.0.0.1}"
|
| 361 |
+
local gw_port="${OPENCLAW_GATEWAY_PORT:-18789}"
|
| 362 |
+
if nc -z "$gw_host" "$gw_port" 2>/dev/null || (echo > "/dev/tcp/$gw_host/$gw_port") 2>/dev/null; then
|
| 363 |
+
ok "Gateway reachable at $gw_host:$gw_port"
|
| 364 |
+
else
|
| 365 |
+
info "Gateway not reachable at $gw_host:$gw_port (start it with: openclaw gateway start)"
|
| 366 |
+
fi
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
# ── Main ──────────────────────────────────────────────────────────────────────
|
| 370 |
+
main() {
|
| 371 |
+
echo ""
|
| 372 |
+
echo " ╔══════════════════════════════════════╗"
|
| 373 |
+
echo " ║ Mission Control Installer ║"
|
| 374 |
+
echo " ║ The mothership for your fleet ║"
|
| 375 |
+
echo " ╚══════════════════════════════════════╝"
|
| 376 |
+
echo ""
|
| 377 |
+
|
| 378 |
+
detect_os
|
| 379 |
+
check_prerequisites
|
| 380 |
+
|
| 381 |
+
# If running from within an existing clone, use current dir
|
| 382 |
+
if [[ -f "$(pwd)/package.json" ]] && grep -q '"mission-control"' "$(pwd)/package.json" 2>/dev/null; then
|
| 383 |
+
INSTALL_DIR="$(pwd)"
|
| 384 |
+
info "Running from existing clone at $INSTALL_DIR"
|
| 385 |
+
else
|
| 386 |
+
fetch_source
|
| 387 |
+
fi
|
| 388 |
+
|
| 389 |
+
# Ensure data directory exists
|
| 390 |
+
mkdir -p "$INSTALL_DIR/.data"
|
| 391 |
+
|
| 392 |
+
setup_env
|
| 393 |
+
|
| 394 |
+
case "$DEPLOY_MODE" in
|
| 395 |
+
docker) deploy_docker ;;
|
| 396 |
+
local) deploy_local ;;
|
| 397 |
+
*) die "Unknown deploy mode: $DEPLOY_MODE" ;;
|
| 398 |
+
esac
|
| 399 |
+
|
| 400 |
+
check_openclaw
|
| 401 |
+
|
| 402 |
+
# ── Print summary ──
|
| 403 |
+
echo ""
|
| 404 |
+
echo " ╔══════════════════════════════════════╗"
|
| 405 |
+
echo " ║ Installation Complete ║"
|
| 406 |
+
echo " ╚══════════════════════════════════════╝"
|
| 407 |
+
echo ""
|
| 408 |
+
info "Dashboard: http://localhost:$MC_PORT"
|
| 409 |
+
info "Mode: $DEPLOY_MODE"
|
| 410 |
+
info "Data: $INSTALL_DIR/.data/"
|
| 411 |
+
echo ""
|
| 412 |
+
info "Credentials are in: $INSTALL_DIR/.env"
|
| 413 |
+
echo ""
|
| 414 |
+
|
| 415 |
+
if [[ "$DEPLOY_MODE" == "docker" ]]; then
|
| 416 |
+
info "Manage:"
|
| 417 |
+
info " docker compose logs -f # view logs"
|
| 418 |
+
info " docker compose restart # restart"
|
| 419 |
+
info " docker compose down # stop"
|
| 420 |
+
else
|
| 421 |
+
info "Manage:"
|
| 422 |
+
info " cat $INSTALL_DIR/.data/mc.log # view logs"
|
| 423 |
+
info " kill \$(cat $INSTALL_DIR/.data/mc.pid) # stop"
|
| 424 |
+
fi
|
| 425 |
+
|
| 426 |
+
echo ""
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
main "$@"
|
|
@@ -1,6 +1,9 @@
|
|
| 1 |
/** @type {import('next').NextConfig} */
|
| 2 |
const nextConfig = {
|
| 3 |
output: 'standalone',
|
|
|
|
|
|
|
|
|
|
| 4 |
turbopack: {},
|
| 5 |
// Transpile ESM-only packages so they resolve correctly in all environments
|
| 6 |
transpilePackages: ['react-markdown', 'remark-gfm'],
|
|
@@ -11,12 +14,13 @@ const nextConfig = {
|
|
| 11 |
|
| 12 |
const csp = [
|
| 13 |
`default-src 'self'`,
|
| 14 |
-
`script-src 'self' 'unsafe-inline'${googleEnabled ? ' https://accounts.google.com' : ''}`,
|
| 15 |
`style-src 'self' 'unsafe-inline'`,
|
| 16 |
-
`connect-src 'self' ws: wss: http://127.0.0.1:* http://localhost:*`,
|
| 17 |
`img-src 'self' data: blob:${googleEnabled ? ' https://*.googleusercontent.com https://lh3.googleusercontent.com' : ''}`,
|
| 18 |
`font-src 'self' data:`,
|
| 19 |
`frame-src 'self'${googleEnabled ? ' https://accounts.google.com' : ''}`,
|
|
|
|
| 20 |
].join('; ')
|
| 21 |
|
| 22 |
return [
|
|
|
|
| 1 |
/** @type {import('next').NextConfig} */
|
| 2 |
const nextConfig = {
|
| 3 |
output: 'standalone',
|
| 4 |
+
outputFileTracingExcludes: {
|
| 5 |
+
'/*': ['./.data/**/*'],
|
| 6 |
+
},
|
| 7 |
turbopack: {},
|
| 8 |
// Transpile ESM-only packages so they resolve correctly in all environments
|
| 9 |
transpilePackages: ['react-markdown', 'remark-gfm'],
|
|
|
|
| 14 |
|
| 15 |
const csp = [
|
| 16 |
`default-src 'self'`,
|
| 17 |
+
`script-src 'self' 'unsafe-inline' blob:${googleEnabled ? ' https://accounts.google.com' : ''}`,
|
| 18 |
`style-src 'self' 'unsafe-inline'`,
|
| 19 |
+
`connect-src 'self' ws: wss: http://127.0.0.1:* http://localhost:* https://cdn.jsdelivr.net`,
|
| 20 |
`img-src 'self' data: blob:${googleEnabled ? ' https://*.googleusercontent.com https://lh3.googleusercontent.com' : ''}`,
|
| 21 |
`font-src 'self' data:`,
|
| 22 |
`frame-src 'self'${googleEnabled ? ' https://accounts.google.com' : ''}`,
|
| 23 |
+
`worker-src 'self' blob:`,
|
| 24 |
].join('; ')
|
| 25 |
|
| 26 |
return [
|
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OpenClaw Gateway Security and Hardening Best Practices
|
| 2 |
+
|
| 3 |
+
This document consolidates security and hardening best practices for the OpenClaw Gateway, drawing from official documentation and recent security advisories.
|
| 4 |
+
|
| 5 |
+
## 1. Core Security Model & Deployment Considerations
|
| 6 |
+
|
| 7 |
+
OpenClaw is designed primarily for a **personal assistant deployment model**, assuming one trusted operator per gateway. It is **not intended for multi-tenant environments** with untrusted or adversarial users. For such scenarios, run separate gateway instances for each trust boundary.
|
| 8 |
+
|
| 9 |
+
## 2. Hardened Baseline Configuration
|
| 10 |
+
|
| 11 |
+
For a secure starting point, consider the following configuration, which keeps the Gateway local, isolates DMs, and disables potentially dangerous tools by default:
|
| 12 |
+
|
| 13 |
+
```json
|
| 14 |
+
{
|
| 15 |
+
"gateway": {
|
| 16 |
+
"mode": "local",
|
| 17 |
+
"bind": "loopback",
|
| 18 |
+
"auth": {
|
| 19 |
+
"mode": "token",
|
| 20 |
+
"token": "replace-with-long-random-token"
|
| 21 |
+
}
|
| 22 |
+
},
|
| 23 |
+
"session": {
|
| 24 |
+
"dmScope": "per-channel-peer"
|
| 25 |
+
},
|
| 26 |
+
"tools": {
|
| 27 |
+
"profile": "messaging",
|
| 28 |
+
"deny": ["group:automation", "group:runtime", "group:fs", "sessions_spawn", "sessions_send"],
|
| 29 |
+
"fs": {
|
| 30 |
+
"workspaceOnly": true
|
| 31 |
+
},
|
| 32 |
+
"exec": {
|
| 33 |
+
"security": "deny",
|
| 34 |
+
"ask": "always"
|
| 35 |
+
}
|
| 36 |
+
},
|
| 37 |
+
"elevated": {
|
| 38 |
+
"enabled": false
|
| 39 |
+
},
|
| 40 |
+
"channels": {
|
| 41 |
+
"whatsapp": {
|
| 42 |
+
"dmPolicy": "pairing",
|
| 43 |
+
"groups": {
|
| 44 |
+
"*": {
|
| 45 |
+
"requireMention": true
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
## 3. Key Hardening Recommendations
|
| 54 |
+
|
| 55 |
+
### 3.1. Network Security
|
| 56 |
+
|
| 57 |
+
* **Do Not Expose Publicly:** Never expose the OpenClaw gateway directly to the public internet. It typically runs on port 18789. Publicly exposed gateways are easily discoverable.
|
| 58 |
+
* **Bind to Localhost:** Configure the gateway to listen only for connections from the local machine by binding it to `127.0.0.1` (localhost) or `loopback` in your `openclaw.json`.
|
| 59 |
+
* **Firewall Rules:** Implement strict firewall rules to block all unnecessary inbound and outbound connections, allowing only essential traffic.
|
| 60 |
+
* **Secure Remote Access:** For remote access, use secure methods like SSH tunneling or a VPN (e.g., Tailscale) instead of direct exposure.
|
| 61 |
+
* **Docker Considerations:** If using Docker, be aware that it can bypass UFW rules. Configure rules in the `DOCKER-USER` chain to control exposure.
|
| 62 |
+
|
| 63 |
+
### 3.2. Authentication and Access Control
|
| 64 |
+
|
| 65 |
+
* **Enable Gateway Authentication:** Always enable gateway authentication and use a strong, randomly generated authentication token. Generate a token with `openclaw doctor --generate-gateway-token`.
|
| 66 |
+
* **Manage Access Tokens:** Treat your gateway authentication token like a password. Rotate it regularly and store it securely (e.g., as an environment variable, not in plaintext config files).
|
| 67 |
+
* **Restrict Chat and Messaging:** If integrating with chat platforms, use allowlists to specify which user IDs can interact with your agent.
|
| 68 |
+
* **Direct Messages (DMs) and Groups:**
|
| 69 |
+
* For DMs, use the default `pairing` policy (`dmPolicy: "pairing"`) to require approval for unknown senders.
|
| 70 |
+
* For group chats, require the bot to be explicitly mentioned to respond (`requireMention: true`).
|
| 71 |
+
* Isolate DM sessions using `session.dmScope: "per-channel-peer"` to prevent context leakage.
|
| 72 |
+
|
| 73 |
+
### 3.3. Isolation and Sandboxing
|
| 74 |
+
|
| 75 |
+
* **Run in a Docker Container:** The recommended approach is to run OpenClaw within a Docker container for process isolation, filesystem restrictions, and network controls.
|
| 76 |
+
* **Harden Docker Configuration:**
|
| 77 |
+
* Do not mount your home directory or the Docker socket.
|
| 78 |
+
* Use read-only filesystems where possible.
|
| 79 |
+
* Drop unnecessary Linux capabilities.
|
| 80 |
+
* Run the container as a non-root user.
|
| 81 |
+
* **Enable Sandbox Mode:** For tasks that execute code, enable OpenClaw's sandbox mode to prevent malicious or compromised prompts from accessing your system or network. Configure this in `agents.defaults.sandbox`.
|
| 82 |
+
|
| 83 |
+
### 3.4. Credential and Secret Management
|
| 84 |
+
|
| 85 |
+
* **Avoid Plaintext Storage:** Never store API keys, tokens, or other sensitive information in plaintext configuration files.
|
| 86 |
+
* **Use Secure Storage Mechanisms:** Load credentials from environment variables or use dedicated secrets management solutions (e.g., Hashicorp Vault, AWS Secrets Manager).
|
| 87 |
+
|
| 88 |
+
### 3.5. File System Permissions
|
| 89 |
+
|
| 90 |
+
* Ensure your configuration and state files are private.
|
| 91 |
+
* `~/.openclaw/openclaw.json` should have permissions `600` (user read/write only).
|
| 92 |
+
* The `~/.openclaw` directory should have permissions `700` (user access only).
|
| 93 |
+
* `~/.openclaw/credentials/` and its contents should also be `600`.
|
| 94 |
+
|
| 95 |
+
### 3.6. Tool and Skill Security
|
| 96 |
+
|
| 97 |
+
* **Principle of Least Privilege:** Only grant the agent the permissions and tools it absolutely needs.
|
| 98 |
+
* **Audit Third-Party Skills:** Be extremely cautious with third-party skills, as they can contain malicious code. Research has shown a significant number of skills on marketplaces may be malicious.
|
| 99 |
+
|
| 100 |
+
### 3.7. Prompt Injection Mitigation
|
| 101 |
+
|
| 102 |
+
* Lock down who can message the bot using DM pairing and allowlists.
|
| 103 |
+
* Require mentions in group chats.
|
| 104 |
+
* Run agents that process untrusted content in a sandbox with a minimal toolset.
|
| 105 |
+
* Use the latest, most powerful models, as they are generally more resistant to prompt injection.
|
| 106 |
+
|
| 107 |
+
### 3.8. Monitoring and Incident Response
|
| 108 |
+
|
| 109 |
+
* **Enable Logging:** Turn on comprehensive logging for all agent activities (command executions, API calls, file access). Store logs in a secure, separate location where the agent cannot modify them.
|
| 110 |
+
* **Log Redaction:** Keep log redaction enabled (`logging.redactSensitive: "tools"`) to prevent sensitive information from leaking into logs.
|
| 111 |
+
* **Incident Response Plan:** Have a plan for suspected compromises, including stopping the gateway and revoking API keys.
|
| 112 |
+
|
| 113 |
+
## 4. Staying Updated and Aware of Vulnerabilities
|
| 114 |
+
|
| 115 |
+
The OpenClaw project is under active development, and new vulnerabilities are discovered.
|
| 116 |
+
|
| 117 |
+
* **Keep Software Updated:** Regularly update OpenClaw and its dependencies to ensure you have the latest security patches.
|
| 118 |
+
* **Be Aware of Recent Threats:** Stay informed about new vulnerabilities. Notable past vulnerabilities include:
|
| 119 |
+
* **ClawJacked (High Severity):** Allowed malicious websites to hijack locally running OpenClaw instances via WebSocket connections and brute-force password. Patched in v2026.2.25.
|
| 120 |
+
* **Remote Code Execution (Critical - CVE-2026-25253):** A malicious link could trick the Control UI into sending an auth token to an attacker-controlled server, leading to RCE. Patched in v2026.1.29.
|
| 121 |
+
* **Authentication Bypass (High Severity - CVE-2026-26327):** Allowed attackers on the same local network to intercept credentials by spoofing a legitimate gateway.
|
| 122 |
+
* **Other Vulnerabilities:** Server-Side Request Forgery (SSRF - CVE-2026-26322), missing webhook authentication (CVE-2026-26319), and path traversal (CVE-2026-26329).
|
| 123 |
+
|
| 124 |
+
By diligently applying these practices, you can significantly enhance the security posture of your OpenClaw Gateway deployment.
|
|
@@ -1,28 +1,33 @@
|
|
| 1 |
{
|
| 2 |
"name": "mission-control",
|
| 3 |
-
"version": "
|
| 4 |
"description": "OpenClaw Mission Control — open-source agent orchestration dashboard",
|
| 5 |
"scripts": {
|
| 6 |
-
"
|
| 7 |
-
"
|
| 8 |
-
"
|
| 9 |
-
"
|
| 10 |
-
"
|
| 11 |
-
"
|
|
|
|
|
|
|
|
|
|
| 12 |
"test:watch": "vitest",
|
| 13 |
"test:ui": "vitest --ui",
|
| 14 |
-
"test:e2e": "playwright test",
|
| 15 |
-
"test:e2e:openclaw:local": "E2E_GATEWAY_EXPECTED=0 playwright test -c playwright.openclaw.local.config.ts",
|
| 16 |
-
"test:e2e:openclaw:gateway": "E2E_GATEWAY_EXPECTED=1 playwright test -c playwright.openclaw.gateway.config.ts",
|
| 17 |
"test:e2e:openclaw": "pnpm test:e2e:openclaw:local && pnpm test:e2e:openclaw:gateway",
|
| 18 |
"test:all": "pnpm lint && pnpm typecheck && pnpm test && pnpm build && pnpm test:e2e",
|
| 19 |
"quality:gate": "pnpm test:all"
|
| 20 |
},
|
| 21 |
"dependencies": {
|
|
|
|
| 22 |
"@scalar/api-reference-react": "^0.8.66",
|
| 23 |
"@xyflow/react": "^12.10.0",
|
| 24 |
"autoprefixer": "^10.4.20",
|
| 25 |
"better-sqlite3": "^12.6.2",
|
|
|
|
| 26 |
"clsx": "^2.1.1",
|
| 27 |
"eslint": "^9.18.0",
|
| 28 |
"eslint-config-next": "^16.1.6",
|
|
@@ -34,6 +39,7 @@
|
|
| 34 |
"react-dom": "^19.0.1",
|
| 35 |
"react-markdown": "^10.1.0",
|
| 36 |
"reactflow": "^11.11.4",
|
|
|
|
| 37 |
"recharts": "^3.7.0",
|
| 38 |
"remark-gfm": "^4.0.1",
|
| 39 |
"tailwind-merge": "^3.4.0",
|
|
@@ -60,7 +66,7 @@
|
|
| 60 |
"vitest": "^2.1.5"
|
| 61 |
},
|
| 62 |
"engines": {
|
| 63 |
-
"node": ">=
|
| 64 |
},
|
| 65 |
"keywords": [
|
| 66 |
"openclaw",
|
|
|
|
| 1 |
{
|
| 2 |
"name": "mission-control",
|
| 3 |
+
"version": "2.0.0",
|
| 4 |
"description": "OpenClaw Mission Control — open-source agent orchestration dashboard",
|
| 5 |
"scripts": {
|
| 6 |
+
"verify:node": "node scripts/check-node-version.mjs",
|
| 7 |
+
"dev": "pnpm run verify:node && next dev --hostname 127.0.0.1 --port ${PORT:-3000}",
|
| 8 |
+
"build": "pnpm run verify:node && next build",
|
| 9 |
+
"start": "pnpm run verify:node && next start --hostname 0.0.0.0 --port ${PORT:-3000}",
|
| 10 |
+
"start:standalone": "pnpm run verify:node && bash scripts/start-standalone.sh",
|
| 11 |
+
"deploy:standalone": "pnpm run verify:node && bash scripts/deploy-standalone.sh",
|
| 12 |
+
"lint": "pnpm run verify:node && eslint .",
|
| 13 |
+
"typecheck": "pnpm run verify:node && tsc --noEmit",
|
| 14 |
+
"test": "pnpm run verify:node && vitest run",
|
| 15 |
"test:watch": "vitest",
|
| 16 |
"test:ui": "vitest --ui",
|
| 17 |
+
"test:e2e": "pnpm run verify:node && playwright test",
|
| 18 |
+
"test:e2e:openclaw:local": "pnpm run verify:node && E2E_GATEWAY_EXPECTED=0 playwright test -c playwright.openclaw.local.config.ts",
|
| 19 |
+
"test:e2e:openclaw:gateway": "pnpm run verify:node && E2E_GATEWAY_EXPECTED=1 playwright test -c playwright.openclaw.gateway.config.ts",
|
| 20 |
"test:e2e:openclaw": "pnpm test:e2e:openclaw:local && pnpm test:e2e:openclaw:gateway",
|
| 21 |
"test:all": "pnpm lint && pnpm typecheck && pnpm test && pnpm build && pnpm test:e2e",
|
| 22 |
"quality:gate": "pnpm test:all"
|
| 23 |
},
|
| 24 |
"dependencies": {
|
| 25 |
+
"@radix-ui/react-slot": "^1.2.4",
|
| 26 |
"@scalar/api-reference-react": "^0.8.66",
|
| 27 |
"@xyflow/react": "^12.10.0",
|
| 28 |
"autoprefixer": "^10.4.20",
|
| 29 |
"better-sqlite3": "^12.6.2",
|
| 30 |
+
"class-variance-authority": "^0.7.1",
|
| 31 |
"clsx": "^2.1.1",
|
| 32 |
"eslint": "^9.18.0",
|
| 33 |
"eslint-config-next": "^16.1.6",
|
|
|
|
| 39 |
"react-dom": "^19.0.1",
|
| 40 |
"react-markdown": "^10.1.0",
|
| 41 |
"reactflow": "^11.11.4",
|
| 42 |
+
"reagraph": "^4.30.8",
|
| 43 |
"recharts": "^3.7.0",
|
| 44 |
"remark-gfm": "^4.0.1",
|
| 45 |
"tailwind-merge": "^3.4.0",
|
|
|
|
| 66 |
"vitest": "^2.1.5"
|
| 67 |
},
|
| 68 |
"engines": {
|
| 69 |
+
"node": ">=22 <23"
|
| 70 |
},
|
| 71 |
"keywords": [
|
| 72 |
"openclaw",
|
|
@@ -18,14 +18,13 @@ export default defineConfig({
|
|
| 18 |
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }
|
| 19 |
],
|
| 20 |
webServer: {
|
| 21 |
-
command: 'node
|
| 22 |
url: 'http://127.0.0.1:3005',
|
| 23 |
reuseExistingServer: true,
|
| 24 |
timeout: 120_000,
|
| 25 |
env: {
|
| 26 |
...process.env,
|
| 27 |
-
|
| 28 |
-
PORT: process.env.PORT || '3005',
|
| 29 |
MC_DISABLE_RATE_LIMIT: process.env.MC_DISABLE_RATE_LIMIT || '1',
|
| 30 |
MC_WORKLOAD_QUEUE_DEPTH_THROTTLE: process.env.MC_WORKLOAD_QUEUE_DEPTH_THROTTLE || '1000',
|
| 31 |
MC_WORKLOAD_QUEUE_DEPTH_SHED: process.env.MC_WORKLOAD_QUEUE_DEPTH_SHED || '2000',
|
|
@@ -34,7 +33,6 @@ export default defineConfig({
|
|
| 34 |
API_KEY: process.env.API_KEY || 'test-api-key-e2e-12345',
|
| 35 |
AUTH_USER: process.env.AUTH_USER || 'testadmin',
|
| 36 |
AUTH_PASS: process.env.AUTH_PASS || 'testpass1234!',
|
| 37 |
-
OPENCLAW_MEMORY_DIR: process.env.OPENCLAW_MEMORY_DIR || '.data/e2e-memory',
|
| 38 |
},
|
| 39 |
}
|
| 40 |
})
|
|
|
|
| 18 |
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }
|
| 19 |
],
|
| 20 |
webServer: {
|
| 21 |
+
command: 'node scripts/e2e-openclaw/start-e2e-server.mjs --mode=local',
|
| 22 |
url: 'http://127.0.0.1:3005',
|
| 23 |
reuseExistingServer: true,
|
| 24 |
timeout: 120_000,
|
| 25 |
env: {
|
| 26 |
...process.env,
|
| 27 |
+
MISSION_CONTROL_TEST_MODE: process.env.MISSION_CONTROL_TEST_MODE || '1',
|
|
|
|
| 28 |
MC_DISABLE_RATE_LIMIT: process.env.MC_DISABLE_RATE_LIMIT || '1',
|
| 29 |
MC_WORKLOAD_QUEUE_DEPTH_THROTTLE: process.env.MC_WORKLOAD_QUEUE_DEPTH_THROTTLE || '1000',
|
| 30 |
MC_WORKLOAD_QUEUE_DEPTH_SHED: process.env.MC_WORKLOAD_QUEUE_DEPTH_SHED || '2000',
|
|
|
|
| 33 |
API_KEY: process.env.API_KEY || 'test-api-key-e2e-12345',
|
| 34 |
AUTH_USER: process.env.AUTH_USER || 'testadmin',
|
| 35 |
AUTH_PASS: process.env.AUTH_PASS || 'testpass1234!',
|
|
|
|
| 36 |
},
|
| 37 |
}
|
| 38 |
})
|
|
@@ -8,6 +8,9 @@ importers:
|
|
| 8 |
|
| 9 |
.:
|
| 10 |
dependencies:
|
|
|
|
|
|
|
|
|
|
| 11 |
'@scalar/api-reference-react':
|
| 12 |
specifier: ^0.8.66
|
| 13 |
version: 0.8.66(react@19.2.4)(tailwindcss@3.4.19(yaml@2.8.2))(typescript@5.9.3)
|
|
@@ -20,6 +23,9 @@ importers:
|
|
| 20 |
better-sqlite3:
|
| 21 |
specifier: ^12.6.2
|
| 22 |
version: 12.6.2
|
|
|
|
|
|
|
|
|
|
| 23 |
clsx:
|
| 24 |
specifier: ^2.1.1
|
| 25 |
version: 2.1.1
|
|
@@ -53,6 +59,9 @@ importers:
|
|
| 53 |
reactflow:
|
| 54 |
specifier: ^11.11.4
|
| 55 |
version: 11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
|
|
|
|
|
|
|
|
|
| 56 |
recharts:
|
| 57 |
specifier: ^3.7.0
|
| 58 |
version: 3.7.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1)
|
|
@@ -306,6 +315,9 @@ packages:
|
|
| 306 |
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
|
| 307 |
engines: {node: '>=18'}
|
| 308 |
|
|
|
|
|
|
|
|
|
|
| 309 |
'@emnapi/core@1.8.1':
|
| 310 |
resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
|
| 311 |
|
|
@@ -736,6 +748,14 @@ packages:
|
|
| 736 |
'@marijn/find-cluster-break@1.0.2':
|
| 737 |
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
|
| 738 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 739 |
'@napi-rs/wasm-runtime@0.2.12':
|
| 740 |
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
| 741 |
|
|
@@ -828,6 +848,88 @@ packages:
|
|
| 828 |
engines: {node: '>=18'}
|
| 829 |
hasBin: true
|
| 830 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 831 |
'@reactflow/background@11.3.14':
|
| 832 |
resolution: {integrity: sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==}
|
| 833 |
peerDependencies:
|
|
@@ -1175,6 +1277,9 @@ packages:
|
|
| 1175 |
'@types/react-dom':
|
| 1176 |
optional: true
|
| 1177 |
|
|
|
|
|
|
|
|
|
|
| 1178 |
'@tybys/wasm-util@0.10.1':
|
| 1179 |
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
| 1180 |
|
|
@@ -1292,6 +1397,9 @@ packages:
|
|
| 1292 |
'@types/debug@4.1.12':
|
| 1293 |
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
| 1294 |
|
|
|
|
|
|
|
|
|
|
| 1295 |
'@types/estree-jsx@1.0.5':
|
| 1296 |
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
|
| 1297 |
|
|
@@ -1322,14 +1430,28 @@ packages:
|
|
| 1322 |
'@types/node@22.19.9':
|
| 1323 |
resolution: {integrity: sha512-PD03/U8g1F9T9MI+1OBisaIARhSzeidsUjQaf51fOxrfjeiKN9bLVO06lHuHYjxdnqLWJijJHfqXPSJri2EM2A==}
|
| 1324 |
|
|
|
|
|
|
|
|
|
|
| 1325 |
'@types/react-dom@19.2.3':
|
| 1326 |
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
|
| 1327 |
peerDependencies:
|
| 1328 |
'@types/react': ^19.2.0
|
| 1329 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1330 |
'@types/react@19.2.13':
|
| 1331 |
resolution: {integrity: sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==}
|
| 1332 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1333 |
'@types/unist@2.0.11':
|
| 1334 |
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
|
| 1335 |
|
|
@@ -1345,6 +1467,9 @@ packages:
|
|
| 1345 |
'@types/web-bluetooth@0.0.21':
|
| 1346 |
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
| 1347 |
|
|
|
|
|
|
|
|
|
|
| 1348 |
'@types/ws@8.18.1':
|
| 1349 |
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
| 1350 |
|
|
@@ -1518,6 +1643,14 @@ packages:
|
|
| 1518 |
cpu: [x64]
|
| 1519 |
os: [win32]
|
| 1520 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1521 |
'@vercel/oidc@3.1.0':
|
| 1522 |
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
|
| 1523 |
engines: {node: '>= 20'}
|
|
@@ -1653,6 +1786,9 @@ packages:
|
|
| 1653 |
peerDependencies:
|
| 1654 |
vue: ^3.5.0
|
| 1655 |
|
|
|
|
|
|
|
|
|
|
| 1656 |
'@xyflow/react@12.10.0':
|
| 1657 |
resolution: {integrity: sha512-eOtz3whDMWrB4KWVatIBrKuxECHqip6PfA8fTpaS2RUGVpiEAe+nqDKsLqkViVWxDGreq0lWX71Xth/SPAzXiw==}
|
| 1658 |
peerDependencies:
|
|
@@ -1662,6 +1798,9 @@ packages:
|
|
| 1662 |
'@xyflow/system@0.0.74':
|
| 1663 |
resolution: {integrity: sha512-7v7B/PkiVrkdZzSbL+inGAo6tkR/WQHHG0/jhSvLQToCsfa8YubOGmBYd1s08tpKpihdHDZFwzQZeR69QSBb4Q==}
|
| 1664 |
|
|
|
|
|
|
|
|
|
|
| 1665 |
acorn-jsx@5.3.2:
|
| 1666 |
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
| 1667 |
peerDependencies:
|
|
@@ -1823,6 +1962,9 @@ packages:
|
|
| 1823 |
resolution: {integrity: sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==}
|
| 1824 |
engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x}
|
| 1825 |
|
|
|
|
|
|
|
|
|
|
| 1826 |
binary-extensions@2.3.0:
|
| 1827 |
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
| 1828 |
engines: {node: '>=8'}
|
|
@@ -1851,6 +1993,9 @@ packages:
|
|
| 1851 |
buffer@5.7.1:
|
| 1852 |
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
| 1853 |
|
|
|
|
|
|
|
|
|
|
| 1854 |
cac@6.7.14:
|
| 1855 |
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
| 1856 |
engines: {node: '>=8'}
|
|
@@ -1875,6 +2020,12 @@ packages:
|
|
| 1875 |
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
|
| 1876 |
engines: {node: '>= 6'}
|
| 1877 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1878 |
caniuse-lite@1.0.30001769:
|
| 1879 |
resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==}
|
| 1880 |
|
|
@@ -1916,9 +2067,15 @@ packages:
|
|
| 1916 |
chownr@1.1.4:
|
| 1917 |
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
| 1918 |
|
|
|
|
|
|
|
|
|
|
| 1919 |
classcat@5.0.5:
|
| 1920 |
resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==}
|
| 1921 |
|
|
|
|
|
|
|
|
|
|
| 1922 |
client-only@0.0.1:
|
| 1923 |
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
| 1924 |
|
|
@@ -1956,6 +2113,11 @@ packages:
|
|
| 1956 |
crelt@1.0.6:
|
| 1957 |
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
| 1958 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1959 |
cross-spawn@7.0.6:
|
| 1960 |
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
| 1961 |
engines: {node: '>= 8'}
|
|
@@ -1995,6 +2157,9 @@ packages:
|
|
| 1995 |
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
|
| 1996 |
engines: {node: '>=12'}
|
| 1997 |
|
|
|
|
|
|
|
|
|
|
| 1998 |
d3-color@3.1.0:
|
| 1999 |
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
| 2000 |
engines: {node: '>=12'}
|
|
@@ -2011,18 +2176,33 @@ packages:
|
|
| 2011 |
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
|
| 2012 |
engines: {node: '>=12'}
|
| 2013 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2014 |
d3-format@3.1.2:
|
| 2015 |
resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==}
|
| 2016 |
engines: {node: '>=12'}
|
| 2017 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2018 |
d3-interpolate@3.0.1:
|
| 2019 |
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
|
| 2020 |
engines: {node: '>=12'}
|
| 2021 |
|
|
|
|
|
|
|
|
|
|
| 2022 |
d3-path@3.1.0:
|
| 2023 |
resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
|
| 2024 |
engines: {node: '>=12'}
|
| 2025 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2026 |
d3-scale@4.0.2:
|
| 2027 |
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
|
| 2028 |
engines: {node: '>=12'}
|
|
@@ -2135,6 +2315,9 @@ packages:
|
|
| 2135 |
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
| 2136 |
engines: {node: '>=6'}
|
| 2137 |
|
|
|
|
|
|
|
|
|
|
| 2138 |
detect-libc@2.1.2:
|
| 2139 |
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
| 2140 |
engines: {node: '>=8'}
|
|
@@ -2158,6 +2341,9 @@ packages:
|
|
| 2158 |
dom-accessibility-api@0.6.3:
|
| 2159 |
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
|
| 2160 |
|
|
|
|
|
|
|
|
|
|
| 2161 |
dunder-proto@1.0.1:
|
| 2162 |
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
| 2163 |
engines: {node: '>= 0.4'}
|
|
@@ -2165,6 +2351,9 @@ packages:
|
|
| 2165 |
electron-to-chromium@1.5.286:
|
| 2166 |
resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==}
|
| 2167 |
|
|
|
|
|
|
|
|
|
|
| 2168 |
emoji-regex@9.2.2:
|
| 2169 |
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
| 2170 |
|
|
@@ -2362,6 +2551,10 @@ packages:
|
|
| 2362 |
eventemitter3@5.0.4:
|
| 2363 |
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
|
| 2364 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2365 |
eventsource-parser@3.0.6:
|
| 2366 |
resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
|
| 2367 |
engines: {node: '>=18.0.0'}
|
|
@@ -2415,6 +2608,12 @@ packages:
|
|
| 2415 |
picomatch:
|
| 2416 |
optional: true
|
| 2417 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2418 |
file-entry-cache@8.0.0:
|
| 2419 |
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
| 2420 |
engines: {node: '>=16.0.0'}
|
|
@@ -2534,10 +2733,56 @@ packages:
|
|
| 2534 |
globrex@0.1.2:
|
| 2535 |
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
|
| 2536 |
|
|
|
|
|
|
|
|
|
|
| 2537 |
gopd@1.2.0:
|
| 2538 |
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
| 2539 |
engines: {node: '>= 0.4'}
|
| 2540 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2541 |
guess-json-indent@3.0.1:
|
| 2542 |
resolution: {integrity: sha512-LWZ3Vr8BG7DHE3TzPYFqkhjNRw4vYgFSsv2nfMuHklAlOfiy54/EwiDQuQfFVLxENCVv20wpbjfTayooQHrEhQ==}
|
| 2543 |
engines: {node: '>=18.18.0'}
|
|
@@ -2639,6 +2884,12 @@ packages:
|
|
| 2639 |
highlightjs-curl@1.3.0:
|
| 2640 |
resolution: {integrity: sha512-50UEfZq1KR0Lfk2Tr6xb/MUIZH3h10oNC0OTy9g7WELcs5Fgy/mKN1vEhuKTkKbdo8vr5F9GXstu2eLhApfQ3A==}
|
| 2641 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2642 |
hookable@6.0.1:
|
| 2643 |
resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==}
|
| 2644 |
|
|
@@ -2682,6 +2933,9 @@ packages:
|
|
| 2682 |
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
|
| 2683 |
engines: {node: '>= 4'}
|
| 2684 |
|
|
|
|
|
|
|
|
|
|
| 2685 |
immer@10.2.0:
|
| 2686 |
resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==}
|
| 2687 |
|
|
@@ -2819,6 +3073,9 @@ packages:
|
|
| 2819 |
is-potential-custom-element-name@1.0.1:
|
| 2820 |
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
|
| 2821 |
|
|
|
|
|
|
|
|
|
|
| 2822 |
is-regex@1.2.1:
|
| 2823 |
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
|
| 2824 |
engines: {node: '>= 0.4'}
|
|
@@ -2869,6 +3126,11 @@ packages:
|
|
| 2869 |
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
|
| 2870 |
engines: {node: '>= 0.4'}
|
| 2871 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2872 |
jiti@1.21.7:
|
| 2873 |
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
|
| 2874 |
hasBin: true
|
|
@@ -2954,6 +3216,9 @@ packages:
|
|
| 2954 |
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
| 2955 |
engines: {node: '>= 0.8.0'}
|
| 2956 |
|
|
|
|
|
|
|
|
|
|
| 2957 |
lilconfig@3.1.3:
|
| 2958 |
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
|
| 2959 |
engines: {node: '>=14'}
|
|
@@ -2991,6 +3256,12 @@ packages:
|
|
| 2991 |
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
|
| 2992 |
hasBin: true
|
| 2993 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2994 |
magic-string@0.30.21:
|
| 2995 |
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
| 2996 |
|
|
@@ -3054,6 +3325,14 @@ packages:
|
|
| 3054 |
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
| 3055 |
engines: {node: '>= 8'}
|
| 3056 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3057 |
microdiff@1.5.0:
|
| 3058 |
resolution: {integrity: sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==}
|
| 3059 |
|
|
@@ -3166,6 +3445,9 @@ packages:
|
|
| 3166 |
mkdirp-classic@0.5.3:
|
| 3167 |
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
| 3168 |
|
|
|
|
|
|
|
|
|
|
| 3169 |
ms@2.1.3:
|
| 3170 |
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
| 3171 |
|
|
@@ -3275,6 +3557,9 @@ packages:
|
|
| 3275 |
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
| 3276 |
engines: {node: '>= 0.4'}
|
| 3277 |
|
|
|
|
|
|
|
|
|
|
| 3278 |
on-exit-leak-free@2.1.2:
|
| 3279 |
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
|
| 3280 |
engines: {node: '>=14.0.0'}
|
|
@@ -3306,6 +3591,9 @@ packages:
|
|
| 3306 |
resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==}
|
| 3307 |
engines: {node: '>=14.16'}
|
| 3308 |
|
|
|
|
|
|
|
|
|
|
| 3309 |
parent-module@1.0.1:
|
| 3310 |
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
| 3311 |
engines: {node: '>=6'}
|
|
@@ -3439,6 +3727,9 @@ packages:
|
|
| 3439 |
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
| 3440 |
engines: {node: ^10 || ^12 || >=14}
|
| 3441 |
|
|
|
|
|
|
|
|
|
|
| 3442 |
prebuild-install@7.1.3:
|
| 3443 |
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
|
| 3444 |
engines: {node: '>=10'}
|
|
@@ -3463,6 +3754,9 @@ packages:
|
|
| 3463 |
process-warning@5.0.0:
|
| 3464 |
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
|
| 3465 |
|
|
|
|
|
|
|
|
|
|
| 3466 |
prop-types@15.8.1:
|
| 3467 |
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
| 3468 |
|
|
@@ -3524,6 +3818,15 @@ packages:
|
|
| 3524 |
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
| 3525 |
engines: {node: '>=0.10.0'}
|
| 3526 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3527 |
react@19.2.4:
|
| 3528 |
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
|
| 3529 |
engines: {node: '>=0.10.0'}
|
|
@@ -3545,6 +3848,13 @@ packages:
|
|
| 3545 |
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
| 3546 |
engines: {node: '>=8.10.0'}
|
| 3547 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3548 |
real-require@0.2.0:
|
| 3549 |
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
| 3550 |
engines: {node: '>= 12.13.0'}
|
|
@@ -3763,6 +4073,15 @@ packages:
|
|
| 3763 |
stackback@0.0.2:
|
| 3764 |
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
| 3765 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3766 |
std-env@3.10.0:
|
| 3767 |
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
| 3768 |
|
|
@@ -3870,6 +4189,11 @@ packages:
|
|
| 3870 |
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
| 3871 |
engines: {node: '>= 0.4'}
|
| 3872 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3873 |
swrv@1.1.0:
|
| 3874 |
resolution: {integrity: sha512-pjllRDr2s0iTwiE5Isvip51dZGR7GjLH1gCSVyE8bQnbAx6xackXsFdojau+1O5u98yHF5V73HQGOFxKUXO9gQ==}
|
| 3875 |
peerDependencies:
|
|
@@ -3911,6 +4235,19 @@ packages:
|
|
| 3911 |
resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==}
|
| 3912 |
engines: {node: '>=20'}
|
| 3913 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3914 |
time-span@5.1.0:
|
| 3915 |
resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==}
|
| 3916 |
engines: {node: '>=12'}
|
|
@@ -3962,6 +4299,19 @@ packages:
|
|
| 3962 |
trim-lines@3.0.1:
|
| 3963 |
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
|
| 3964 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3965 |
trough@2.2.0:
|
| 3966 |
resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
|
| 3967 |
|
|
@@ -4001,6 +4351,9 @@ packages:
|
|
| 4001 |
tunnel-agent@0.6.0:
|
| 4002 |
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
| 4003 |
|
|
|
|
|
|
|
|
|
|
| 4004 |
type-check@0.4.0:
|
| 4005 |
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
| 4006 |
engines: {node: '>= 0.8.0'}
|
|
@@ -4092,6 +4445,10 @@ packages:
|
|
| 4092 |
util-deprecate@1.0.2:
|
| 4093 |
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
| 4094 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4095 |
vfile-location@5.0.3:
|
| 4096 |
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
|
| 4097 |
|
|
@@ -4216,6 +4573,12 @@ packages:
|
|
| 4216 |
web-worker@1.5.0:
|
| 4217 |
resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==}
|
| 4218 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4219 |
webidl-conversions@7.0.0:
|
| 4220 |
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
| 4221 |
engines: {node: '>=12'}
|
|
@@ -4339,6 +4702,24 @@ packages:
|
|
| 4339 |
use-sync-external-store:
|
| 4340 |
optional: true
|
| 4341 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4342 |
zwitch@2.0.4:
|
| 4343 |
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
| 4344 |
|
|
@@ -4611,6 +4992,8 @@ snapshots:
|
|
| 4611 |
|
| 4612 |
'@csstools/css-tokenizer@3.0.4': {}
|
| 4613 |
|
|
|
|
|
|
|
| 4614 |
'@emnapi/core@1.8.1':
|
| 4615 |
dependencies:
|
| 4616 |
'@emnapi/wasi-threads': 1.1.0
|
|
@@ -4954,6 +5337,13 @@ snapshots:
|
|
| 4954 |
|
| 4955 |
'@marijn/find-cluster-break@1.0.2': {}
|
| 4956 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4957 |
'@napi-rs/wasm-runtime@0.2.12':
|
| 4958 |
dependencies:
|
| 4959 |
'@emnapi/core': 1.8.1
|
|
@@ -5015,6 +5405,105 @@ snapshots:
|
|
| 5015 |
dependencies:
|
| 5016 |
playwright: 1.58.2
|
| 5017 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5018 |
'@reactflow/background@11.3.14(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
| 5019 |
dependencies:
|
| 5020 |
'@reactflow/core': 11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
|
@@ -5612,6 +6101,8 @@ snapshots:
|
|
| 5612 |
'@types/react': 19.2.13
|
| 5613 |
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
| 5614 |
|
|
|
|
|
|
|
| 5615 |
'@tybys/wasm-util@0.10.1':
|
| 5616 |
dependencies:
|
| 5617 |
tslib: 2.8.1
|
|
@@ -5765,6 +6256,8 @@ snapshots:
|
|
| 5765 |
dependencies:
|
| 5766 |
'@types/ms': 2.1.0
|
| 5767 |
|
|
|
|
|
|
|
| 5768 |
'@types/estree-jsx@1.0.5':
|
| 5769 |
dependencies:
|
| 5770 |
'@types/estree': 1.0.8
|
|
@@ -5793,14 +6286,32 @@ snapshots:
|
|
| 5793 |
dependencies:
|
| 5794 |
undici-types: 6.21.0
|
| 5795 |
|
|
|
|
|
|
|
| 5796 |
'@types/react-dom@19.2.3(@types/react@19.2.13)':
|
| 5797 |
dependencies:
|
| 5798 |
'@types/react': 19.2.13
|
| 5799 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5800 |
'@types/react@19.2.13':
|
| 5801 |
dependencies:
|
| 5802 |
csstype: 3.2.3
|
| 5803 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5804 |
'@types/unist@2.0.11': {}
|
| 5805 |
|
| 5806 |
'@types/unist@3.0.3': {}
|
|
@@ -5811,6 +6322,8 @@ snapshots:
|
|
| 5811 |
|
| 5812 |
'@types/web-bluetooth@0.0.21': {}
|
| 5813 |
|
|
|
|
|
|
|
| 5814 |
'@types/ws@8.18.1':
|
| 5815 |
dependencies:
|
| 5816 |
'@types/node': 22.19.9
|
|
@@ -5973,6 +6486,13 @@ snapshots:
|
|
| 5973 |
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
|
| 5974 |
optional: true
|
| 5975 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5976 |
'@vercel/oidc@3.1.0': {}
|
| 5977 |
|
| 5978 |
'@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@22.19.9))':
|
|
@@ -6124,6 +6644,8 @@ snapshots:
|
|
| 6124 |
dependencies:
|
| 6125 |
vue: 3.5.29(typescript@5.9.3)
|
| 6126 |
|
|
|
|
|
|
|
| 6127 |
'@xyflow/react@12.10.0(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
| 6128 |
dependencies:
|
| 6129 |
'@xyflow/system': 0.0.74
|
|
@@ -6147,6 +6669,8 @@ snapshots:
|
|
| 6147 |
d3-selection: 3.0.0
|
| 6148 |
d3-zoom: 3.0.0
|
| 6149 |
|
|
|
|
|
|
|
| 6150 |
acorn-jsx@5.3.2(acorn@8.15.0):
|
| 6151 |
dependencies:
|
| 6152 |
acorn: 8.15.0
|
|
@@ -6319,6 +6843,10 @@ snapshots:
|
|
| 6319 |
bindings: 1.5.0
|
| 6320 |
prebuild-install: 7.1.3
|
| 6321 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6322 |
binary-extensions@2.3.0: {}
|
| 6323 |
|
| 6324 |
bindings@1.5.0:
|
|
@@ -6357,6 +6885,11 @@ snapshots:
|
|
| 6357 |
base64-js: 1.5.1
|
| 6358 |
ieee754: 1.2.1
|
| 6359 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6360 |
cac@6.7.14: {}
|
| 6361 |
|
| 6362 |
call-bind-apply-helpers@1.0.2:
|
|
@@ -6380,6 +6913,10 @@ snapshots:
|
|
| 6380 |
|
| 6381 |
camelcase-css@2.0.1: {}
|
| 6382 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6383 |
caniuse-lite@1.0.30001769: {}
|
| 6384 |
|
| 6385 |
ccount@2.0.1: {}
|
|
@@ -6423,8 +6960,14 @@ snapshots:
|
|
| 6423 |
|
| 6424 |
chownr@1.1.4: {}
|
| 6425 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6426 |
classcat@5.0.5: {}
|
| 6427 |
|
|
|
|
|
|
|
| 6428 |
client-only@0.0.1: {}
|
| 6429 |
|
| 6430 |
clsx@2.1.1: {}
|
|
@@ -6449,6 +6992,10 @@ snapshots:
|
|
| 6449 |
|
| 6450 |
crelt@1.0.6: {}
|
| 6451 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6452 |
cross-spawn@7.0.6:
|
| 6453 |
dependencies:
|
| 6454 |
path-key: 3.1.1
|
|
@@ -6482,6 +7029,8 @@ snapshots:
|
|
| 6482 |
dependencies:
|
| 6483 |
internmap: 2.0.3
|
| 6484 |
|
|
|
|
|
|
|
| 6485 |
d3-color@3.1.0: {}
|
| 6486 |
|
| 6487 |
d3-dispatch@3.0.1: {}
|
|
@@ -6493,14 +7042,28 @@ snapshots:
|
|
| 6493 |
|
| 6494 |
d3-ease@3.0.1: {}
|
| 6495 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6496 |
d3-format@3.1.2: {}
|
| 6497 |
|
|
|
|
|
|
|
| 6498 |
d3-interpolate@3.0.1:
|
| 6499 |
dependencies:
|
| 6500 |
d3-color: 3.1.0
|
| 6501 |
|
|
|
|
|
|
|
| 6502 |
d3-path@3.1.0: {}
|
| 6503 |
|
|
|
|
|
|
|
| 6504 |
d3-scale@4.0.2:
|
| 6505 |
dependencies:
|
| 6506 |
d3-array: 3.2.4
|
|
@@ -6611,6 +7174,10 @@ snapshots:
|
|
| 6611 |
|
| 6612 |
dequal@2.0.3: {}
|
| 6613 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6614 |
detect-libc@2.1.2: {}
|
| 6615 |
|
| 6616 |
devlop@1.1.0:
|
|
@@ -6629,6 +7196,8 @@ snapshots:
|
|
| 6629 |
|
| 6630 |
dom-accessibility-api@0.6.3: {}
|
| 6631 |
|
|
|
|
|
|
|
| 6632 |
dunder-proto@1.0.1:
|
| 6633 |
dependencies:
|
| 6634 |
call-bind-apply-helpers: 1.0.2
|
|
@@ -6637,6 +7206,8 @@ snapshots:
|
|
| 6637 |
|
| 6638 |
electron-to-chromium@1.5.286: {}
|
| 6639 |
|
|
|
|
|
|
|
| 6640 |
emoji-regex@9.2.2: {}
|
| 6641 |
|
| 6642 |
end-of-stream@1.4.5:
|
|
@@ -6997,6 +7568,8 @@ snapshots:
|
|
| 6997 |
|
| 6998 |
eventemitter3@5.0.4: {}
|
| 6999 |
|
|
|
|
|
|
|
| 7000 |
eventsource-parser@3.0.6: {}
|
| 7001 |
|
| 7002 |
expand-template@2.0.3: {}
|
|
@@ -7041,6 +7614,10 @@ snapshots:
|
|
| 7041 |
optionalDependencies:
|
| 7042 |
picomatch: 4.0.3
|
| 7043 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7044 |
file-entry-cache@8.0.0:
|
| 7045 |
dependencies:
|
| 7046 |
flat-cache: 4.0.1
|
|
@@ -7155,8 +7732,60 @@ snapshots:
|
|
| 7155 |
|
| 7156 |
globrex@0.1.2: {}
|
| 7157 |
|
|
|
|
|
|
|
| 7158 |
gopd@1.2.0: {}
|
| 7159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7160 |
guess-json-indent@3.0.1: {}
|
| 7161 |
|
| 7162 |
has-bigints@1.1.0: {}
|
|
@@ -7345,6 +7974,10 @@ snapshots:
|
|
| 7345 |
|
| 7346 |
highlightjs-curl@1.3.0: {}
|
| 7347 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7348 |
hookable@6.0.1: {}
|
| 7349 |
|
| 7350 |
html-encoding-sniffer@4.0.0:
|
|
@@ -7385,6 +8018,8 @@ snapshots:
|
|
| 7385 |
|
| 7386 |
ignore@7.0.5: {}
|
| 7387 |
|
|
|
|
|
|
|
| 7388 |
immer@10.2.0: {}
|
| 7389 |
|
| 7390 |
immer@11.1.3: {}
|
|
@@ -7513,6 +8148,8 @@ snapshots:
|
|
| 7513 |
|
| 7514 |
is-potential-custom-element-name@1.0.1: {}
|
| 7515 |
|
|
|
|
|
|
|
| 7516 |
is-regex@1.2.1:
|
| 7517 |
dependencies:
|
| 7518 |
call-bound: 1.0.4
|
|
@@ -7567,6 +8204,13 @@ snapshots:
|
|
| 7567 |
has-symbols: 1.1.0
|
| 7568 |
set-function-name: 2.0.2
|
| 7569 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7570 |
jiti@1.21.7: {}
|
| 7571 |
|
| 7572 |
joycon@3.1.1: {}
|
|
@@ -7652,6 +8296,10 @@ snapshots:
|
|
| 7652 |
prelude-ls: 1.2.1
|
| 7653 |
type-check: 0.4.0
|
| 7654 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7655 |
lilconfig@3.1.3: {}
|
| 7656 |
|
| 7657 |
lines-and-columns@1.2.4: {}
|
|
@@ -7684,6 +8332,11 @@ snapshots:
|
|
| 7684 |
|
| 7685 |
lz-string@1.5.0: {}
|
| 7686 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7687 |
magic-string@0.30.21:
|
| 7688 |
dependencies:
|
| 7689 |
'@jridgewell/sourcemap-codec': 1.5.5
|
|
@@ -7853,6 +8506,12 @@ snapshots:
|
|
| 7853 |
|
| 7854 |
merge2@1.4.1: {}
|
| 7855 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7856 |
microdiff@1.5.0: {}
|
| 7857 |
|
| 7858 |
micromark-core-commonmark@2.0.3:
|
|
@@ -8067,6 +8726,10 @@ snapshots:
|
|
| 8067 |
|
| 8068 |
mkdirp-classic@0.5.3: {}
|
| 8069 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8070 |
ms@2.1.3: {}
|
| 8071 |
|
| 8072 |
mz@2.7.0:
|
|
@@ -8174,6 +8837,8 @@ snapshots:
|
|
| 8174 |
define-properties: 1.2.1
|
| 8175 |
es-object-atoms: 1.1.1
|
| 8176 |
|
|
|
|
|
|
|
| 8177 |
on-exit-leak-free@2.1.2: {}
|
| 8178 |
|
| 8179 |
once@1.4.0:
|
|
@@ -8209,6 +8874,10 @@ snapshots:
|
|
| 8209 |
|
| 8210 |
p-timeout@6.1.4: {}
|
| 8211 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8212 |
parent-module@1.0.1:
|
| 8213 |
dependencies:
|
| 8214 |
callsites: 3.1.0
|
|
@@ -8341,6 +9010,8 @@ snapshots:
|
|
| 8341 |
picocolors: 1.1.1
|
| 8342 |
source-map-js: 1.2.1
|
| 8343 |
|
|
|
|
|
|
|
| 8344 |
prebuild-install@7.1.3:
|
| 8345 |
dependencies:
|
| 8346 |
detect-libc: 2.1.2
|
|
@@ -8372,6 +9043,11 @@ snapshots:
|
|
| 8372 |
|
| 8373 |
process-warning@5.0.0: {}
|
| 8374 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8375 |
prop-types@15.8.1:
|
| 8376 |
dependencies:
|
| 8377 |
loose-envify: 1.4.0
|
|
@@ -8453,6 +9129,12 @@ snapshots:
|
|
| 8453 |
|
| 8454 |
react-refresh@0.17.0: {}
|
| 8455 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8456 |
react@19.2.4: {}
|
| 8457 |
|
| 8458 |
reactflow@11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
|
@@ -8483,6 +9165,43 @@ snapshots:
|
|
| 8483 |
dependencies:
|
| 8484 |
picomatch: 2.3.1
|
| 8485 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8486 |
real-require@0.2.0: {}
|
| 8487 |
|
| 8488 |
recharts@3.7.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1):
|
|
@@ -8819,6 +9538,13 @@ snapshots:
|
|
| 8819 |
|
| 8820 |
stackback@0.0.2: {}
|
| 8821 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8822 |
std-env@3.10.0: {}
|
| 8823 |
|
| 8824 |
stop-iteration-iterator@1.1.0:
|
|
@@ -8947,6 +9673,10 @@ snapshots:
|
|
| 8947 |
|
| 8948 |
supports-preserve-symlinks-flag@1.0.0: {}
|
| 8949 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8950 |
swrv@1.1.0(vue@3.5.29(typescript@5.9.3)):
|
| 8951 |
dependencies:
|
| 8952 |
vue: 3.5.29(typescript@5.9.3)
|
|
@@ -9014,6 +9744,22 @@ snapshots:
|
|
| 9014 |
dependencies:
|
| 9015 |
real-require: 0.2.0
|
| 9016 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9017 |
time-span@5.1.0:
|
| 9018 |
dependencies:
|
| 9019 |
convert-hrtime: 5.0.0
|
|
@@ -9055,6 +9801,20 @@ snapshots:
|
|
| 9055 |
|
| 9056 |
trim-lines@3.0.1: {}
|
| 9057 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9058 |
trough@2.2.0: {}
|
| 9059 |
|
| 9060 |
truncate-json@3.0.1:
|
|
@@ -9088,6 +9848,14 @@ snapshots:
|
|
| 9088 |
dependencies:
|
| 9089 |
safe-buffer: 5.2.1
|
| 9090 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9091 |
type-check@0.4.0:
|
| 9092 |
dependencies:
|
| 9093 |
prelude-ls: 1.2.1
|
|
@@ -9235,6 +10003,8 @@ snapshots:
|
|
| 9235 |
|
| 9236 |
util-deprecate@1.0.2: {}
|
| 9237 |
|
|
|
|
|
|
|
| 9238 |
vfile-location@5.0.3:
|
| 9239 |
dependencies:
|
| 9240 |
'@types/unist': 3.0.3
|
|
@@ -9374,6 +10144,10 @@ snapshots:
|
|
| 9374 |
|
| 9375 |
web-worker@1.5.0: {}
|
| 9376 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9377 |
webidl-conversions@7.0.0: {}
|
| 9378 |
|
| 9379 |
whatwg-encoding@3.1.1:
|
|
@@ -9474,4 +10248,11 @@ snapshots:
|
|
| 9474 |
react: 19.2.4
|
| 9475 |
use-sync-external-store: 1.6.0(react@19.2.4)
|
| 9476 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9477 |
zwitch@2.0.4: {}
|
|
|
|
| 8 |
|
| 9 |
.:
|
| 10 |
dependencies:
|
| 11 |
+
'@radix-ui/react-slot':
|
| 12 |
+
specifier: ^1.2.4
|
| 13 |
+
version: 1.2.4(@types/react@19.2.13)(react@19.2.4)
|
| 14 |
'@scalar/api-reference-react':
|
| 15 |
specifier: ^0.8.66
|
| 16 |
version: 0.8.66(react@19.2.4)(tailwindcss@3.4.19(yaml@2.8.2))(typescript@5.9.3)
|
|
|
|
| 23 |
better-sqlite3:
|
| 24 |
specifier: ^12.6.2
|
| 25 |
version: 12.6.2
|
| 26 |
+
class-variance-authority:
|
| 27 |
+
specifier: ^0.7.1
|
| 28 |
+
version: 0.7.1
|
| 29 |
clsx:
|
| 30 |
specifier: ^2.1.1
|
| 31 |
version: 2.1.1
|
|
|
|
| 59 |
reactflow:
|
| 60 |
specifier: ^11.11.4
|
| 61 |
version: 11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
| 62 |
+
reagraph:
|
| 63 |
+
specifier: ^4.30.8
|
| 64 |
+
version: 4.30.8(@types/react@19.2.13)(@types/three@0.183.1)(graphology-types@0.24.8)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
|
| 65 |
recharts:
|
| 66 |
specifier: ^3.7.0
|
| 67 |
version: 3.7.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1)
|
|
|
|
| 315 |
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
|
| 316 |
engines: {node: '>=18'}
|
| 317 |
|
| 318 |
+
'@dimforge/rapier3d-compat@0.12.0':
|
| 319 |
+
resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
|
| 320 |
+
|
| 321 |
'@emnapi/core@1.8.1':
|
| 322 |
resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
|
| 323 |
|
|
|
|
| 748 |
'@marijn/find-cluster-break@1.0.2':
|
| 749 |
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
|
| 750 |
|
| 751 |
+
'@mediapipe/tasks-vision@0.10.17':
|
| 752 |
+
resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==}
|
| 753 |
+
|
| 754 |
+
'@monogrid/gainmap-js@3.4.0':
|
| 755 |
+
resolution: {integrity: sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==}
|
| 756 |
+
peerDependencies:
|
| 757 |
+
three: '>= 0.159.0'
|
| 758 |
+
|
| 759 |
'@napi-rs/wasm-runtime@0.2.12':
|
| 760 |
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
| 761 |
|
|
|
|
| 848 |
engines: {node: '>=18'}
|
| 849 |
hasBin: true
|
| 850 |
|
| 851 |
+
'@radix-ui/react-compose-refs@1.1.2':
|
| 852 |
+
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
|
| 853 |
+
peerDependencies:
|
| 854 |
+
'@types/react': '*'
|
| 855 |
+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
| 856 |
+
peerDependenciesMeta:
|
| 857 |
+
'@types/react':
|
| 858 |
+
optional: true
|
| 859 |
+
|
| 860 |
+
'@radix-ui/react-slot@1.2.4':
|
| 861 |
+
resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==}
|
| 862 |
+
peerDependencies:
|
| 863 |
+
'@types/react': '*'
|
| 864 |
+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
| 865 |
+
peerDependenciesMeta:
|
| 866 |
+
'@types/react':
|
| 867 |
+
optional: true
|
| 868 |
+
|
| 869 |
+
'@react-spring/animated@10.0.3':
|
| 870 |
+
resolution: {integrity: sha512-7MrxADV3vaUADn2V9iYhaIL6iOWRx9nCJjYrsk2AHD2kwPr6fg7Pt0v+deX5RnCDmCKNnD6W5fasiyM8D+wzJQ==}
|
| 871 |
+
peerDependencies:
|
| 872 |
+
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
| 873 |
+
|
| 874 |
+
'@react-spring/core@10.0.3':
|
| 875 |
+
resolution: {integrity: sha512-D4DwNO68oohDf/0HG2G0Uragzb9IA1oXblxrd6MZAcBcUQG2EHUWXewjdECMPLNmQvlYVyyBRH6gPxXM5DX7DQ==}
|
| 876 |
+
peerDependencies:
|
| 877 |
+
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
| 878 |
+
|
| 879 |
+
'@react-spring/rafz@10.0.3':
|
| 880 |
+
resolution: {integrity: sha512-Ri2/xqt8OnQ2iFKkxKMSF4Nqv0LSWnxXT4jXFzBDsHgeeH/cHxTLupAWUwmV9hAGgmEhBmh5aONtj3J6R/18wg==}
|
| 881 |
+
|
| 882 |
+
'@react-spring/shared@10.0.3':
|
| 883 |
+
resolution: {integrity: sha512-geCal66nrkaQzUVhPkGomylo+Jpd5VPK8tPMEDevQEfNSWAQP15swHm+MCRG4wVQrQlTi9lOzKzpRoTL3CA84Q==}
|
| 884 |
+
peerDependencies:
|
| 885 |
+
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
| 886 |
+
|
| 887 |
+
'@react-spring/three@10.0.3':
|
| 888 |
+
resolution: {integrity: sha512-hZP7ChF/EwnWn+H2xuzAsRRfQdhquoBTI1HKgO6X9V8tcVCuR69qJmsA9N00CA4Nzx0bo/zwBtqONmi55Ffm5w==}
|
| 889 |
+
peerDependencies:
|
| 890 |
+
'@react-three/fiber': '>=6.0'
|
| 891 |
+
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
| 892 |
+
three: '>=0.126'
|
| 893 |
+
|
| 894 |
+
'@react-spring/types@10.0.3':
|
| 895 |
+
resolution: {integrity: sha512-H5Ixkd2OuSIgHtxuHLTt7aJYfhMXKXT/rK32HPD/kSrOB6q6ooeiWAXkBy7L8F3ZxdkBb9ini9zP9UwnEFzWgQ==}
|
| 896 |
+
|
| 897 |
+
'@react-three/drei@10.7.7':
|
| 898 |
+
resolution: {integrity: sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==}
|
| 899 |
+
peerDependencies:
|
| 900 |
+
'@react-three/fiber': ^9.0.0
|
| 901 |
+
react: ^19
|
| 902 |
+
react-dom: ^19
|
| 903 |
+
three: '>=0.159'
|
| 904 |
+
peerDependenciesMeta:
|
| 905 |
+
react-dom:
|
| 906 |
+
optional: true
|
| 907 |
+
|
| 908 |
+
'@react-three/fiber@9.5.0':
|
| 909 |
+
resolution: {integrity: sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==}
|
| 910 |
+
peerDependencies:
|
| 911 |
+
expo: '>=43.0'
|
| 912 |
+
expo-asset: '>=8.4'
|
| 913 |
+
expo-file-system: '>=11.0'
|
| 914 |
+
expo-gl: '>=11.0'
|
| 915 |
+
react: '>=19 <19.3'
|
| 916 |
+
react-dom: '>=19 <19.3'
|
| 917 |
+
react-native: '>=0.78'
|
| 918 |
+
three: '>=0.156'
|
| 919 |
+
peerDependenciesMeta:
|
| 920 |
+
expo:
|
| 921 |
+
optional: true
|
| 922 |
+
expo-asset:
|
| 923 |
+
optional: true
|
| 924 |
+
expo-file-system:
|
| 925 |
+
optional: true
|
| 926 |
+
expo-gl:
|
| 927 |
+
optional: true
|
| 928 |
+
react-dom:
|
| 929 |
+
optional: true
|
| 930 |
+
react-native:
|
| 931 |
+
optional: true
|
| 932 |
+
|
| 933 |
'@reactflow/background@11.3.14':
|
| 934 |
resolution: {integrity: sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==}
|
| 935 |
peerDependencies:
|
|
|
|
| 1277 |
'@types/react-dom':
|
| 1278 |
optional: true
|
| 1279 |
|
| 1280 |
+
'@tweenjs/tween.js@23.1.3':
|
| 1281 |
+
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
|
| 1282 |
+
|
| 1283 |
'@tybys/wasm-util@0.10.1':
|
| 1284 |
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
| 1285 |
|
|
|
|
| 1397 |
'@types/debug@4.1.12':
|
| 1398 |
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
| 1399 |
|
| 1400 |
+
'@types/draco3d@1.4.10':
|
| 1401 |
+
resolution: {integrity: sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==}
|
| 1402 |
+
|
| 1403 |
'@types/estree-jsx@1.0.5':
|
| 1404 |
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
|
| 1405 |
|
|
|
|
| 1430 |
'@types/node@22.19.9':
|
| 1431 |
resolution: {integrity: sha512-PD03/U8g1F9T9MI+1OBisaIARhSzeidsUjQaf51fOxrfjeiKN9bLVO06lHuHYjxdnqLWJijJHfqXPSJri2EM2A==}
|
| 1432 |
|
| 1433 |
+
'@types/offscreencanvas@2019.7.3':
|
| 1434 |
+
resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==}
|
| 1435 |
+
|
| 1436 |
'@types/react-dom@19.2.3':
|
| 1437 |
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
|
| 1438 |
peerDependencies:
|
| 1439 |
'@types/react': ^19.2.0
|
| 1440 |
|
| 1441 |
+
'@types/react-reconciler@0.28.9':
|
| 1442 |
+
resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==}
|
| 1443 |
+
peerDependencies:
|
| 1444 |
+
'@types/react': '*'
|
| 1445 |
+
|
| 1446 |
'@types/react@19.2.13':
|
| 1447 |
resolution: {integrity: sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==}
|
| 1448 |
|
| 1449 |
+
'@types/stats.js@0.17.4':
|
| 1450 |
+
resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==}
|
| 1451 |
+
|
| 1452 |
+
'@types/three@0.183.1':
|
| 1453 |
+
resolution: {integrity: sha512-f2Pu5Hrepfgavttdye3PsH5RWyY/AvdZQwIVhrc4uNtvF7nOWJacQKcoVJn0S4f0yYbmAE6AR+ve7xDcuYtMGw==}
|
| 1454 |
+
|
| 1455 |
'@types/unist@2.0.11':
|
| 1456 |
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
|
| 1457 |
|
|
|
|
| 1467 |
'@types/web-bluetooth@0.0.21':
|
| 1468 |
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
| 1469 |
|
| 1470 |
+
'@types/webxr@0.5.24':
|
| 1471 |
+
resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==}
|
| 1472 |
+
|
| 1473 |
'@types/ws@8.18.1':
|
| 1474 |
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
| 1475 |
|
|
|
|
| 1643 |
cpu: [x64]
|
| 1644 |
os: [win32]
|
| 1645 |
|
| 1646 |
+
'@use-gesture/core@10.3.1':
|
| 1647 |
+
resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==}
|
| 1648 |
+
|
| 1649 |
+
'@use-gesture/react@10.3.1':
|
| 1650 |
+
resolution: {integrity: sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==}
|
| 1651 |
+
peerDependencies:
|
| 1652 |
+
react: '>= 16.8.0'
|
| 1653 |
+
|
| 1654 |
'@vercel/oidc@3.1.0':
|
| 1655 |
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
|
| 1656 |
engines: {node: '>= 20'}
|
|
|
|
| 1786 |
peerDependencies:
|
| 1787 |
vue: ^3.5.0
|
| 1788 |
|
| 1789 |
+
'@webgpu/types@0.1.69':
|
| 1790 |
+
resolution: {integrity: sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==}
|
| 1791 |
+
|
| 1792 |
'@xyflow/react@12.10.0':
|
| 1793 |
resolution: {integrity: sha512-eOtz3whDMWrB4KWVatIBrKuxECHqip6PfA8fTpaS2RUGVpiEAe+nqDKsLqkViVWxDGreq0lWX71Xth/SPAzXiw==}
|
| 1794 |
peerDependencies:
|
|
|
|
| 1798 |
'@xyflow/system@0.0.74':
|
| 1799 |
resolution: {integrity: sha512-7v7B/PkiVrkdZzSbL+inGAo6tkR/WQHHG0/jhSvLQToCsfa8YubOGmBYd1s08tpKpihdHDZFwzQZeR69QSBb4Q==}
|
| 1800 |
|
| 1801 |
+
'@yomguithereal/helpers@1.1.1':
|
| 1802 |
+
resolution: {integrity: sha512-UYvAq/XCA7xoh1juWDYsq3W0WywOB+pz8cgVnE1b45ZfdMhBvHDrgmSFG3jXeZSr2tMTYLGHFHON+ekG05Jebg==}
|
| 1803 |
+
|
| 1804 |
acorn-jsx@5.3.2:
|
| 1805 |
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
| 1806 |
peerDependencies:
|
|
|
|
| 1962 |
resolution: {integrity: sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==}
|
| 1963 |
engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x}
|
| 1964 |
|
| 1965 |
+
bidi-js@1.0.3:
|
| 1966 |
+
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
|
| 1967 |
+
|
| 1968 |
binary-extensions@2.3.0:
|
| 1969 |
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
| 1970 |
engines: {node: '>=8'}
|
|
|
|
| 1993 |
buffer@5.7.1:
|
| 1994 |
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
| 1995 |
|
| 1996 |
+
buffer@6.0.3:
|
| 1997 |
+
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
| 1998 |
+
|
| 1999 |
cac@6.7.14:
|
| 2000 |
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
| 2001 |
engines: {node: '>=8'}
|
|
|
|
| 2020 |
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
|
| 2021 |
engines: {node: '>= 6'}
|
| 2022 |
|
| 2023 |
+
camera-controls@3.1.2:
|
| 2024 |
+
resolution: {integrity: sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==}
|
| 2025 |
+
engines: {node: '>=22.0.0', npm: '>=10.5.1'}
|
| 2026 |
+
peerDependencies:
|
| 2027 |
+
three: '>=0.126.1'
|
| 2028 |
+
|
| 2029 |
caniuse-lite@1.0.30001769:
|
| 2030 |
resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==}
|
| 2031 |
|
|
|
|
| 2067 |
chownr@1.1.4:
|
| 2068 |
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
| 2069 |
|
| 2070 |
+
class-variance-authority@0.7.1:
|
| 2071 |
+
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
| 2072 |
+
|
| 2073 |
classcat@5.0.5:
|
| 2074 |
resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==}
|
| 2075 |
|
| 2076 |
+
classnames@2.5.1:
|
| 2077 |
+
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
|
| 2078 |
+
|
| 2079 |
client-only@0.0.1:
|
| 2080 |
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
| 2081 |
|
|
|
|
| 2113 |
crelt@1.0.6:
|
| 2114 |
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
| 2115 |
|
| 2116 |
+
cross-env@7.0.3:
|
| 2117 |
+
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
|
| 2118 |
+
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
|
| 2119 |
+
hasBin: true
|
| 2120 |
+
|
| 2121 |
cross-spawn@7.0.6:
|
| 2122 |
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
| 2123 |
engines: {node: '>= 8'}
|
|
|
|
| 2157 |
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
|
| 2158 |
engines: {node: '>=12'}
|
| 2159 |
|
| 2160 |
+
d3-binarytree@1.0.2:
|
| 2161 |
+
resolution: {integrity: sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==}
|
| 2162 |
+
|
| 2163 |
d3-color@3.1.0:
|
| 2164 |
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
| 2165 |
engines: {node: '>=12'}
|
|
|
|
| 2176 |
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
|
| 2177 |
engines: {node: '>=12'}
|
| 2178 |
|
| 2179 |
+
d3-force-3d@3.0.6:
|
| 2180 |
+
resolution: {integrity: sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==}
|
| 2181 |
+
engines: {node: '>=12'}
|
| 2182 |
+
|
| 2183 |
d3-format@3.1.2:
|
| 2184 |
resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==}
|
| 2185 |
engines: {node: '>=12'}
|
| 2186 |
|
| 2187 |
+
d3-hierarchy@3.1.2:
|
| 2188 |
+
resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
|
| 2189 |
+
engines: {node: '>=12'}
|
| 2190 |
+
|
| 2191 |
d3-interpolate@3.0.1:
|
| 2192 |
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
|
| 2193 |
engines: {node: '>=12'}
|
| 2194 |
|
| 2195 |
+
d3-octree@1.1.0:
|
| 2196 |
+
resolution: {integrity: sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==}
|
| 2197 |
+
|
| 2198 |
d3-path@3.1.0:
|
| 2199 |
resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
|
| 2200 |
engines: {node: '>=12'}
|
| 2201 |
|
| 2202 |
+
d3-quadtree@3.0.1:
|
| 2203 |
+
resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
|
| 2204 |
+
engines: {node: '>=12'}
|
| 2205 |
+
|
| 2206 |
d3-scale@4.0.2:
|
| 2207 |
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
|
| 2208 |
engines: {node: '>=12'}
|
|
|
|
| 2315 |
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
| 2316 |
engines: {node: '>=6'}
|
| 2317 |
|
| 2318 |
+
detect-gpu@5.0.70:
|
| 2319 |
+
resolution: {integrity: sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==}
|
| 2320 |
+
|
| 2321 |
detect-libc@2.1.2:
|
| 2322 |
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
| 2323 |
engines: {node: '>=8'}
|
|
|
|
| 2341 |
dom-accessibility-api@0.6.3:
|
| 2342 |
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
|
| 2343 |
|
| 2344 |
+
draco3d@1.5.7:
|
| 2345 |
+
resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==}
|
| 2346 |
+
|
| 2347 |
dunder-proto@1.0.1:
|
| 2348 |
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
| 2349 |
engines: {node: '>= 0.4'}
|
|
|
|
| 2351 |
electron-to-chromium@1.5.286:
|
| 2352 |
resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==}
|
| 2353 |
|
| 2354 |
+
ellipsize@0.6.2:
|
| 2355 |
+
resolution: {integrity: sha512-zB4m5iEETalVrrP8RzcF0Qzqyw3MkUQ4R43NiczRAp0Hpp0+0bRdwKnoaFXyJoVJCipm2/3xc7Hkg0OOAorUPw==}
|
| 2356 |
+
|
| 2357 |
emoji-regex@9.2.2:
|
| 2358 |
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
| 2359 |
|
|
|
|
| 2551 |
eventemitter3@5.0.4:
|
| 2552 |
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
|
| 2553 |
|
| 2554 |
+
events@3.3.0:
|
| 2555 |
+
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
| 2556 |
+
engines: {node: '>=0.8.x'}
|
| 2557 |
+
|
| 2558 |
eventsource-parser@3.0.6:
|
| 2559 |
resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
|
| 2560 |
engines: {node: '>=18.0.0'}
|
|
|
|
| 2608 |
picomatch:
|
| 2609 |
optional: true
|
| 2610 |
|
| 2611 |
+
fflate@0.6.10:
|
| 2612 |
+
resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==}
|
| 2613 |
+
|
| 2614 |
+
fflate@0.8.2:
|
| 2615 |
+
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
|
| 2616 |
+
|
| 2617 |
file-entry-cache@8.0.0:
|
| 2618 |
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
| 2619 |
engines: {node: '>=16.0.0'}
|
|
|
|
| 2733 |
globrex@0.1.2:
|
| 2734 |
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
|
| 2735 |
|
| 2736 |
+
glsl-noise@0.0.0:
|
| 2737 |
+
resolution: {integrity: sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==}
|
| 2738 |
+
|
| 2739 |
gopd@1.2.0:
|
| 2740 |
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
| 2741 |
engines: {node: '>= 0.4'}
|
| 2742 |
|
| 2743 |
+
graphology-indices@0.17.0:
|
| 2744 |
+
resolution: {integrity: sha512-A7RXuKQvdqSWOpn7ZVQo4S33O0vCfPBnUSf7FwE0zNCasqwZVUaCXePuWo5HBpWw68KJcwObZDHpFk6HKH6MYQ==}
|
| 2745 |
+
peerDependencies:
|
| 2746 |
+
graphology-types: '>=0.20.0'
|
| 2747 |
+
|
| 2748 |
+
graphology-layout-forceatlas2@0.10.1:
|
| 2749 |
+
resolution: {integrity: sha512-ogzBeF1FvWzjkikrIFwxhlZXvD2+wlY54lqhsrWprcdPjopM2J9HoMweUmIgwaTvY4bUYVimpSsOdvDv1gPRFQ==}
|
| 2750 |
+
peerDependencies:
|
| 2751 |
+
graphology-types: '>=0.19.0'
|
| 2752 |
+
|
| 2753 |
+
graphology-layout-noverlap@0.4.2:
|
| 2754 |
+
resolution: {integrity: sha512-13WwZSx96zim6l1dfZONcqLh3oqyRcjIBsqz2c2iJ3ohgs3605IDWjldH41Gnhh462xGB1j6VGmuGhZ2FKISXA==}
|
| 2755 |
+
peerDependencies:
|
| 2756 |
+
graphology-types: '>=0.19.0'
|
| 2757 |
+
|
| 2758 |
+
graphology-layout@0.6.1:
|
| 2759 |
+
resolution: {integrity: sha512-m9aMvbd0uDPffUCFPng5ibRkb2pmfNvdKjQWeZrf71RS1aOoat5874+DcyNfMeCT4aQguKC7Lj9eCbqZj/h8Ag==}
|
| 2760 |
+
peerDependencies:
|
| 2761 |
+
graphology-types: '>=0.19.0'
|
| 2762 |
+
|
| 2763 |
+
graphology-metrics@2.4.0:
|
| 2764 |
+
resolution: {integrity: sha512-7WOfOP+mFLCaTJx55Qg4eY+211vr1/b3D/R3biz3SXGhAaCVcWYkfabnmO4O4WBNWANEHtVnFrGgJ0kj6MM6xw==}
|
| 2765 |
+
peerDependencies:
|
| 2766 |
+
graphology-types: '>=0.20.0'
|
| 2767 |
+
|
| 2768 |
+
graphology-shortest-path@2.1.0:
|
| 2769 |
+
resolution: {integrity: sha512-KbT9CTkP/u72vGEJzyRr24xFC7usI9Es3LMmCPHGwQ1KTsoZjxwA9lMKxfU0syvT/w+7fZUdB/Hu2wWYcJBm6Q==}
|
| 2770 |
+
peerDependencies:
|
| 2771 |
+
graphology-types: '>=0.20.0'
|
| 2772 |
+
|
| 2773 |
+
graphology-types@0.24.8:
|
| 2774 |
+
resolution: {integrity: sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==}
|
| 2775 |
+
|
| 2776 |
+
graphology-utils@2.5.2:
|
| 2777 |
+
resolution: {integrity: sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==}
|
| 2778 |
+
peerDependencies:
|
| 2779 |
+
graphology-types: '>=0.23.0'
|
| 2780 |
+
|
| 2781 |
+
graphology@0.26.0:
|
| 2782 |
+
resolution: {integrity: sha512-8SSImzgUUYC89Z042s+0r/vMibY7GX/Emz4LDO5e7jYXhuoWfHISPFJYjpRLUSJGq6UQ6xlenvX1p/hJdfXuXg==}
|
| 2783 |
+
peerDependencies:
|
| 2784 |
+
graphology-types: '>=0.24.0'
|
| 2785 |
+
|
| 2786 |
guess-json-indent@3.0.1:
|
| 2787 |
resolution: {integrity: sha512-LWZ3Vr8BG7DHE3TzPYFqkhjNRw4vYgFSsv2nfMuHklAlOfiy54/EwiDQuQfFVLxENCVv20wpbjfTayooQHrEhQ==}
|
| 2788 |
engines: {node: '>=18.18.0'}
|
|
|
|
| 2884 |
highlightjs-curl@1.3.0:
|
| 2885 |
resolution: {integrity: sha512-50UEfZq1KR0Lfk2Tr6xb/MUIZH3h10oNC0OTy9g7WELcs5Fgy/mKN1vEhuKTkKbdo8vr5F9GXstu2eLhApfQ3A==}
|
| 2886 |
|
| 2887 |
+
hls.js@1.6.15:
|
| 2888 |
+
resolution: {integrity: sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==}
|
| 2889 |
+
|
| 2890 |
+
hold-event@1.1.2:
|
| 2891 |
+
resolution: {integrity: sha512-Bx0A6OBY70cs23orUWk0DuBAAeJjEbmyg8Gnye9+M8+XeWy2CcmRyfiJhTnQQz9s25r9SYjici3URy176MFs5A==}
|
| 2892 |
+
|
| 2893 |
hookable@6.0.1:
|
| 2894 |
resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==}
|
| 2895 |
|
|
|
|
| 2933 |
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
|
| 2934 |
engines: {node: '>= 4'}
|
| 2935 |
|
| 2936 |
+
immediate@3.0.6:
|
| 2937 |
+
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
|
| 2938 |
+
|
| 2939 |
immer@10.2.0:
|
| 2940 |
resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==}
|
| 2941 |
|
|
|
|
| 3073 |
is-potential-custom-element-name@1.0.1:
|
| 3074 |
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
|
| 3075 |
|
| 3076 |
+
is-promise@2.2.2:
|
| 3077 |
+
resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
|
| 3078 |
+
|
| 3079 |
is-regex@1.2.1:
|
| 3080 |
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
|
| 3081 |
engines: {node: '>= 0.4'}
|
|
|
|
| 3126 |
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
|
| 3127 |
engines: {node: '>= 0.4'}
|
| 3128 |
|
| 3129 |
+
its-fine@2.0.0:
|
| 3130 |
+
resolution: {integrity: sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==}
|
| 3131 |
+
peerDependencies:
|
| 3132 |
+
react: ^19.0.0
|
| 3133 |
+
|
| 3134 |
jiti@1.21.7:
|
| 3135 |
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
|
| 3136 |
hasBin: true
|
|
|
|
| 3216 |
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
| 3217 |
engines: {node: '>= 0.8.0'}
|
| 3218 |
|
| 3219 |
+
lie@3.3.0:
|
| 3220 |
+
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
|
| 3221 |
+
|
| 3222 |
lilconfig@3.1.3:
|
| 3223 |
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
|
| 3224 |
engines: {node: '>=14'}
|
|
|
|
| 3256 |
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
|
| 3257 |
hasBin: true
|
| 3258 |
|
| 3259 |
+
maath@0.10.8:
|
| 3260 |
+
resolution: {integrity: sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==}
|
| 3261 |
+
peerDependencies:
|
| 3262 |
+
'@types/three': '>=0.134.0'
|
| 3263 |
+
three: '>=0.134.0'
|
| 3264 |
+
|
| 3265 |
magic-string@0.30.21:
|
| 3266 |
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
| 3267 |
|
|
|
|
| 3325 |
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
| 3326 |
engines: {node: '>= 8'}
|
| 3327 |
|
| 3328 |
+
meshline@3.3.1:
|
| 3329 |
+
resolution: {integrity: sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==}
|
| 3330 |
+
peerDependencies:
|
| 3331 |
+
three: '>=0.137'
|
| 3332 |
+
|
| 3333 |
+
meshoptimizer@1.0.1:
|
| 3334 |
+
resolution: {integrity: sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g==}
|
| 3335 |
+
|
| 3336 |
microdiff@1.5.0:
|
| 3337 |
resolution: {integrity: sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==}
|
| 3338 |
|
|
|
|
| 3445 |
mkdirp-classic@0.5.3:
|
| 3446 |
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
| 3447 |
|
| 3448 |
+
mnemonist@0.39.8:
|
| 3449 |
+
resolution: {integrity: sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==}
|
| 3450 |
+
|
| 3451 |
ms@2.1.3:
|
| 3452 |
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
| 3453 |
|
|
|
|
| 3557 |
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
| 3558 |
engines: {node: '>= 0.4'}
|
| 3559 |
|
| 3560 |
+
obliterator@2.0.5:
|
| 3561 |
+
resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==}
|
| 3562 |
+
|
| 3563 |
on-exit-leak-free@2.1.2:
|
| 3564 |
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
|
| 3565 |
engines: {node: '>=14.0.0'}
|
|
|
|
| 3591 |
resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==}
|
| 3592 |
engines: {node: '>=14.16'}
|
| 3593 |
|
| 3594 |
+
pandemonium@2.4.1:
|
| 3595 |
+
resolution: {integrity: sha512-wRqjisUyiUfXowgm7MFH2rwJzKIr20rca5FsHXCMNm1W5YPP1hCtrZfgmQ62kP7OZ7Xt+cR858aB28lu5NX55g==}
|
| 3596 |
+
|
| 3597 |
parent-module@1.0.1:
|
| 3598 |
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
| 3599 |
engines: {node: '>=6'}
|
|
|
|
| 3727 |
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
| 3728 |
engines: {node: ^10 || ^12 || >=14}
|
| 3729 |
|
| 3730 |
+
potpack@1.0.2:
|
| 3731 |
+
resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==}
|
| 3732 |
+
|
| 3733 |
prebuild-install@7.1.3:
|
| 3734 |
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
|
| 3735 |
engines: {node: '>=10'}
|
|
|
|
| 3754 |
process-warning@5.0.0:
|
| 3755 |
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
|
| 3756 |
|
| 3757 |
+
promise-worker-transferable@1.0.4:
|
| 3758 |
+
resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==}
|
| 3759 |
+
|
| 3760 |
prop-types@15.8.1:
|
| 3761 |
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
| 3762 |
|
|
|
|
| 3818 |
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
| 3819 |
engines: {node: '>=0.10.0'}
|
| 3820 |
|
| 3821 |
+
react-use-measure@2.1.7:
|
| 3822 |
+
resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==}
|
| 3823 |
+
peerDependencies:
|
| 3824 |
+
react: '>=16.13'
|
| 3825 |
+
react-dom: '>=16.13'
|
| 3826 |
+
peerDependenciesMeta:
|
| 3827 |
+
react-dom:
|
| 3828 |
+
optional: true
|
| 3829 |
+
|
| 3830 |
react@19.2.4:
|
| 3831 |
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
|
| 3832 |
engines: {node: '>=0.10.0'}
|
|
|
|
| 3848 |
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
| 3849 |
engines: {node: '>=8.10.0'}
|
| 3850 |
|
| 3851 |
+
reagraph@4.30.8:
|
| 3852 |
+
resolution: {integrity: sha512-DXmG2lAM5k7LQiTQJ4sad8Ks8Q1c98303USwwN+mxpJ5GelBYLjMVG+DPct4CE8ezON1WUoE8PL3RUXiEua22Q==}
|
| 3853 |
+
engines: {node: ^20.19.0 || >=22.12.0}
|
| 3854 |
+
peerDependencies:
|
| 3855 |
+
react: '>=16'
|
| 3856 |
+
react-dom: '>=16'
|
| 3857 |
+
|
| 3858 |
real-require@0.2.0:
|
| 3859 |
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
| 3860 |
engines: {node: '>= 12.13.0'}
|
|
|
|
| 4073 |
stackback@0.0.2:
|
| 4074 |
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
| 4075 |
|
| 4076 |
+
stats-gl@2.4.2:
|
| 4077 |
+
resolution: {integrity: sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==}
|
| 4078 |
+
peerDependencies:
|
| 4079 |
+
'@types/three': '*'
|
| 4080 |
+
three: '*'
|
| 4081 |
+
|
| 4082 |
+
stats.js@0.17.0:
|
| 4083 |
+
resolution: {integrity: sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==}
|
| 4084 |
+
|
| 4085 |
std-env@3.10.0:
|
| 4086 |
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
| 4087 |
|
|
|
|
| 4189 |
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
| 4190 |
engines: {node: '>= 0.4'}
|
| 4191 |
|
| 4192 |
+
suspend-react@0.1.3:
|
| 4193 |
+
resolution: {integrity: sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==}
|
| 4194 |
+
peerDependencies:
|
| 4195 |
+
react: '>=17.0'
|
| 4196 |
+
|
| 4197 |
swrv@1.1.0:
|
| 4198 |
resolution: {integrity: sha512-pjllRDr2s0iTwiE5Isvip51dZGR7GjLH1gCSVyE8bQnbAx6xackXsFdojau+1O5u98yHF5V73HQGOFxKUXO9gQ==}
|
| 4199 |
peerDependencies:
|
|
|
|
| 4235 |
resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==}
|
| 4236 |
engines: {node: '>=20'}
|
| 4237 |
|
| 4238 |
+
three-mesh-bvh@0.8.3:
|
| 4239 |
+
resolution: {integrity: sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==}
|
| 4240 |
+
peerDependencies:
|
| 4241 |
+
three: '>= 0.159.0'
|
| 4242 |
+
|
| 4243 |
+
three-stdlib@2.36.1:
|
| 4244 |
+
resolution: {integrity: sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==}
|
| 4245 |
+
peerDependencies:
|
| 4246 |
+
three: '>=0.128.0'
|
| 4247 |
+
|
| 4248 |
+
three@0.180.0:
|
| 4249 |
+
resolution: {integrity: sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==}
|
| 4250 |
+
|
| 4251 |
time-span@5.1.0:
|
| 4252 |
resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==}
|
| 4253 |
engines: {node: '>=12'}
|
|
|
|
| 4299 |
trim-lines@3.0.1:
|
| 4300 |
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
|
| 4301 |
|
| 4302 |
+
troika-three-text@0.52.4:
|
| 4303 |
+
resolution: {integrity: sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==}
|
| 4304 |
+
peerDependencies:
|
| 4305 |
+
three: '>=0.125.0'
|
| 4306 |
+
|
| 4307 |
+
troika-three-utils@0.52.4:
|
| 4308 |
+
resolution: {integrity: sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==}
|
| 4309 |
+
peerDependencies:
|
| 4310 |
+
three: '>=0.125.0'
|
| 4311 |
+
|
| 4312 |
+
troika-worker-utils@0.52.0:
|
| 4313 |
+
resolution: {integrity: sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==}
|
| 4314 |
+
|
| 4315 |
trough@2.2.0:
|
| 4316 |
resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
|
| 4317 |
|
|
|
|
| 4351 |
tunnel-agent@0.6.0:
|
| 4352 |
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
| 4353 |
|
| 4354 |
+
tunnel-rat@0.1.2:
|
| 4355 |
+
resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==}
|
| 4356 |
+
|
| 4357 |
type-check@0.4.0:
|
| 4358 |
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
| 4359 |
engines: {node: '>= 0.8.0'}
|
|
|
|
| 4445 |
util-deprecate@1.0.2:
|
| 4446 |
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
| 4447 |
|
| 4448 |
+
utility-types@3.11.0:
|
| 4449 |
+
resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==}
|
| 4450 |
+
engines: {node: '>= 4'}
|
| 4451 |
+
|
| 4452 |
vfile-location@5.0.3:
|
| 4453 |
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
|
| 4454 |
|
|
|
|
| 4573 |
web-worker@1.5.0:
|
| 4574 |
resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==}
|
| 4575 |
|
| 4576 |
+
webgl-constants@1.1.1:
|
| 4577 |
+
resolution: {integrity: sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==}
|
| 4578 |
+
|
| 4579 |
+
webgl-sdf-generator@1.1.1:
|
| 4580 |
+
resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==}
|
| 4581 |
+
|
| 4582 |
webidl-conversions@7.0.0:
|
| 4583 |
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
| 4584 |
engines: {node: '>=12'}
|
|
|
|
| 4702 |
use-sync-external-store:
|
| 4703 |
optional: true
|
| 4704 |
|
| 4705 |
+
zustand@5.0.8:
|
| 4706 |
+
resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==}
|
| 4707 |
+
engines: {node: '>=12.20.0'}
|
| 4708 |
+
peerDependencies:
|
| 4709 |
+
'@types/react': '>=18.0.0'
|
| 4710 |
+
immer: '>=9.0.6'
|
| 4711 |
+
react: '>=18.0.0'
|
| 4712 |
+
use-sync-external-store: '>=1.2.0'
|
| 4713 |
+
peerDependenciesMeta:
|
| 4714 |
+
'@types/react':
|
| 4715 |
+
optional: true
|
| 4716 |
+
immer:
|
| 4717 |
+
optional: true
|
| 4718 |
+
react:
|
| 4719 |
+
optional: true
|
| 4720 |
+
use-sync-external-store:
|
| 4721 |
+
optional: true
|
| 4722 |
+
|
| 4723 |
zwitch@2.0.4:
|
| 4724 |
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
| 4725 |
|
|
|
|
| 4992 |
|
| 4993 |
'@csstools/css-tokenizer@3.0.4': {}
|
| 4994 |
|
| 4995 |
+
'@dimforge/rapier3d-compat@0.12.0': {}
|
| 4996 |
+
|
| 4997 |
'@emnapi/core@1.8.1':
|
| 4998 |
dependencies:
|
| 4999 |
'@emnapi/wasi-threads': 1.1.0
|
|
|
|
| 5337 |
|
| 5338 |
'@marijn/find-cluster-break@1.0.2': {}
|
| 5339 |
|
| 5340 |
+
'@mediapipe/tasks-vision@0.10.17': {}
|
| 5341 |
+
|
| 5342 |
+
'@monogrid/gainmap-js@3.4.0(three@0.180.0)':
|
| 5343 |
+
dependencies:
|
| 5344 |
+
promise-worker-transferable: 1.0.4
|
| 5345 |
+
three: 0.180.0
|
| 5346 |
+
|
| 5347 |
'@napi-rs/wasm-runtime@0.2.12':
|
| 5348 |
dependencies:
|
| 5349 |
'@emnapi/core': 1.8.1
|
|
|
|
| 5405 |
dependencies:
|
| 5406 |
playwright: 1.58.2
|
| 5407 |
|
| 5408 |
+
'@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.13)(react@19.2.4)':
|
| 5409 |
+
dependencies:
|
| 5410 |
+
react: 19.2.4
|
| 5411 |
+
optionalDependencies:
|
| 5412 |
+
'@types/react': 19.2.13
|
| 5413 |
+
|
| 5414 |
+
'@radix-ui/react-slot@1.2.4(@types/react@19.2.13)(react@19.2.4)':
|
| 5415 |
+
dependencies:
|
| 5416 |
+
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
| 5417 |
+
react: 19.2.4
|
| 5418 |
+
optionalDependencies:
|
| 5419 |
+
'@types/react': 19.2.13
|
| 5420 |
+
|
| 5421 |
+
'@react-spring/animated@10.0.3(react@19.2.4)':
|
| 5422 |
+
dependencies:
|
| 5423 |
+
'@react-spring/shared': 10.0.3(react@19.2.4)
|
| 5424 |
+
'@react-spring/types': 10.0.3
|
| 5425 |
+
react: 19.2.4
|
| 5426 |
+
|
| 5427 |
+
'@react-spring/core@10.0.3(react@19.2.4)':
|
| 5428 |
+
dependencies:
|
| 5429 |
+
'@react-spring/animated': 10.0.3(react@19.2.4)
|
| 5430 |
+
'@react-spring/shared': 10.0.3(react@19.2.4)
|
| 5431 |
+
'@react-spring/types': 10.0.3
|
| 5432 |
+
react: 19.2.4
|
| 5433 |
+
|
| 5434 |
+
'@react-spring/rafz@10.0.3': {}
|
| 5435 |
+
|
| 5436 |
+
'@react-spring/shared@10.0.3(react@19.2.4)':
|
| 5437 |
+
dependencies:
|
| 5438 |
+
'@react-spring/rafz': 10.0.3
|
| 5439 |
+
'@react-spring/types': 10.0.3
|
| 5440 |
+
react: 19.2.4
|
| 5441 |
+
|
| 5442 |
+
'@react-spring/three@10.0.3(@react-three/fiber@9.5.0(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.180.0))(react@19.2.4)(three@0.180.0)':
|
| 5443 |
+
dependencies:
|
| 5444 |
+
'@react-spring/animated': 10.0.3(react@19.2.4)
|
| 5445 |
+
'@react-spring/core': 10.0.3(react@19.2.4)
|
| 5446 |
+
'@react-spring/shared': 10.0.3(react@19.2.4)
|
| 5447 |
+
'@react-spring/types': 10.0.3
|
| 5448 |
+
'@react-three/fiber': 9.5.0(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.180.0)
|
| 5449 |
+
react: 19.2.4
|
| 5450 |
+
three: 0.180.0
|
| 5451 |
+
|
| 5452 |
+
'@react-spring/types@10.0.3': {}
|
| 5453 |
+
|
| 5454 |
+
'@react-three/drei@10.7.7(@react-three/fiber@9.5.0(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.180.0))(@types/react@19.2.13)(@types/three@0.183.1)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.180.0)':
|
| 5455 |
+
dependencies:
|
| 5456 |
+
'@babel/runtime': 7.28.6
|
| 5457 |
+
'@mediapipe/tasks-vision': 0.10.17
|
| 5458 |
+
'@monogrid/gainmap-js': 3.4.0(three@0.180.0)
|
| 5459 |
+
'@react-three/fiber': 9.5.0(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.180.0)
|
| 5460 |
+
'@use-gesture/react': 10.3.1(react@19.2.4)
|
| 5461 |
+
camera-controls: 3.1.2(three@0.180.0)
|
| 5462 |
+
cross-env: 7.0.3
|
| 5463 |
+
detect-gpu: 5.0.70
|
| 5464 |
+
glsl-noise: 0.0.0
|
| 5465 |
+
hls.js: 1.6.15
|
| 5466 |
+
maath: 0.10.8(@types/three@0.183.1)(three@0.180.0)
|
| 5467 |
+
meshline: 3.3.1(three@0.180.0)
|
| 5468 |
+
react: 19.2.4
|
| 5469 |
+
stats-gl: 2.4.2(@types/three@0.183.1)(three@0.180.0)
|
| 5470 |
+
stats.js: 0.17.0
|
| 5471 |
+
suspend-react: 0.1.3(react@19.2.4)
|
| 5472 |
+
three: 0.180.0
|
| 5473 |
+
three-mesh-bvh: 0.8.3(three@0.180.0)
|
| 5474 |
+
three-stdlib: 2.36.1(three@0.180.0)
|
| 5475 |
+
troika-three-text: 0.52.4(three@0.180.0)
|
| 5476 |
+
tunnel-rat: 0.1.2(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4)
|
| 5477 |
+
use-sync-external-store: 1.6.0(react@19.2.4)
|
| 5478 |
+
utility-types: 3.11.0
|
| 5479 |
+
zustand: 5.0.11(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
|
| 5480 |
+
optionalDependencies:
|
| 5481 |
+
react-dom: 19.2.4(react@19.2.4)
|
| 5482 |
+
transitivePeerDependencies:
|
| 5483 |
+
- '@types/react'
|
| 5484 |
+
- '@types/three'
|
| 5485 |
+
- immer
|
| 5486 |
+
|
| 5487 |
+
'@react-three/fiber@9.5.0(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.180.0)':
|
| 5488 |
+
dependencies:
|
| 5489 |
+
'@babel/runtime': 7.28.6
|
| 5490 |
+
'@types/webxr': 0.5.24
|
| 5491 |
+
base64-js: 1.5.1
|
| 5492 |
+
buffer: 6.0.3
|
| 5493 |
+
its-fine: 2.0.0(@types/react@19.2.13)(react@19.2.4)
|
| 5494 |
+
react: 19.2.4
|
| 5495 |
+
react-use-measure: 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
| 5496 |
+
scheduler: 0.27.0
|
| 5497 |
+
suspend-react: 0.1.3(react@19.2.4)
|
| 5498 |
+
three: 0.180.0
|
| 5499 |
+
use-sync-external-store: 1.6.0(react@19.2.4)
|
| 5500 |
+
zustand: 5.0.11(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
|
| 5501 |
+
optionalDependencies:
|
| 5502 |
+
react-dom: 19.2.4(react@19.2.4)
|
| 5503 |
+
transitivePeerDependencies:
|
| 5504 |
+
- '@types/react'
|
| 5505 |
+
- immer
|
| 5506 |
+
|
| 5507 |
'@reactflow/background@11.3.14(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
| 5508 |
dependencies:
|
| 5509 |
'@reactflow/core': 11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
|
|
|
| 6101 |
'@types/react': 19.2.13
|
| 6102 |
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
| 6103 |
|
| 6104 |
+
'@tweenjs/tween.js@23.1.3': {}
|
| 6105 |
+
|
| 6106 |
'@tybys/wasm-util@0.10.1':
|
| 6107 |
dependencies:
|
| 6108 |
tslib: 2.8.1
|
|
|
|
| 6256 |
dependencies:
|
| 6257 |
'@types/ms': 2.1.0
|
| 6258 |
|
| 6259 |
+
'@types/draco3d@1.4.10': {}
|
| 6260 |
+
|
| 6261 |
'@types/estree-jsx@1.0.5':
|
| 6262 |
dependencies:
|
| 6263 |
'@types/estree': 1.0.8
|
|
|
|
| 6286 |
dependencies:
|
| 6287 |
undici-types: 6.21.0
|
| 6288 |
|
| 6289 |
+
'@types/offscreencanvas@2019.7.3': {}
|
| 6290 |
+
|
| 6291 |
'@types/react-dom@19.2.3(@types/react@19.2.13)':
|
| 6292 |
dependencies:
|
| 6293 |
'@types/react': 19.2.13
|
| 6294 |
|
| 6295 |
+
'@types/react-reconciler@0.28.9(@types/react@19.2.13)':
|
| 6296 |
+
dependencies:
|
| 6297 |
+
'@types/react': 19.2.13
|
| 6298 |
+
|
| 6299 |
'@types/react@19.2.13':
|
| 6300 |
dependencies:
|
| 6301 |
csstype: 3.2.3
|
| 6302 |
|
| 6303 |
+
'@types/stats.js@0.17.4': {}
|
| 6304 |
+
|
| 6305 |
+
'@types/three@0.183.1':
|
| 6306 |
+
dependencies:
|
| 6307 |
+
'@dimforge/rapier3d-compat': 0.12.0
|
| 6308 |
+
'@tweenjs/tween.js': 23.1.3
|
| 6309 |
+
'@types/stats.js': 0.17.4
|
| 6310 |
+
'@types/webxr': 0.5.24
|
| 6311 |
+
'@webgpu/types': 0.1.69
|
| 6312 |
+
fflate: 0.8.2
|
| 6313 |
+
meshoptimizer: 1.0.1
|
| 6314 |
+
|
| 6315 |
'@types/unist@2.0.11': {}
|
| 6316 |
|
| 6317 |
'@types/unist@3.0.3': {}
|
|
|
|
| 6322 |
|
| 6323 |
'@types/web-bluetooth@0.0.21': {}
|
| 6324 |
|
| 6325 |
+
'@types/webxr@0.5.24': {}
|
| 6326 |
+
|
| 6327 |
'@types/ws@8.18.1':
|
| 6328 |
dependencies:
|
| 6329 |
'@types/node': 22.19.9
|
|
|
|
| 6486 |
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
|
| 6487 |
optional: true
|
| 6488 |
|
| 6489 |
+
'@use-gesture/core@10.3.1': {}
|
| 6490 |
+
|
| 6491 |
+
'@use-gesture/react@10.3.1(react@19.2.4)':
|
| 6492 |
+
dependencies:
|
| 6493 |
+
'@use-gesture/core': 10.3.1
|
| 6494 |
+
react: 19.2.4
|
| 6495 |
+
|
| 6496 |
'@vercel/oidc@3.1.0': {}
|
| 6497 |
|
| 6498 |
'@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@22.19.9))':
|
|
|
|
| 6644 |
dependencies:
|
| 6645 |
vue: 3.5.29(typescript@5.9.3)
|
| 6646 |
|
| 6647 |
+
'@webgpu/types@0.1.69': {}
|
| 6648 |
+
|
| 6649 |
'@xyflow/react@12.10.0(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
| 6650 |
dependencies:
|
| 6651 |
'@xyflow/system': 0.0.74
|
|
|
|
| 6669 |
d3-selection: 3.0.0
|
| 6670 |
d3-zoom: 3.0.0
|
| 6671 |
|
| 6672 |
+
'@yomguithereal/helpers@1.1.1': {}
|
| 6673 |
+
|
| 6674 |
acorn-jsx@5.3.2(acorn@8.15.0):
|
| 6675 |
dependencies:
|
| 6676 |
acorn: 8.15.0
|
|
|
|
| 6843 |
bindings: 1.5.0
|
| 6844 |
prebuild-install: 7.1.3
|
| 6845 |
|
| 6846 |
+
bidi-js@1.0.3:
|
| 6847 |
+
dependencies:
|
| 6848 |
+
require-from-string: 2.0.2
|
| 6849 |
+
|
| 6850 |
binary-extensions@2.3.0: {}
|
| 6851 |
|
| 6852 |
bindings@1.5.0:
|
|
|
|
| 6885 |
base64-js: 1.5.1
|
| 6886 |
ieee754: 1.2.1
|
| 6887 |
|
| 6888 |
+
buffer@6.0.3:
|
| 6889 |
+
dependencies:
|
| 6890 |
+
base64-js: 1.5.1
|
| 6891 |
+
ieee754: 1.2.1
|
| 6892 |
+
|
| 6893 |
cac@6.7.14: {}
|
| 6894 |
|
| 6895 |
call-bind-apply-helpers@1.0.2:
|
|
|
|
| 6913 |
|
| 6914 |
camelcase-css@2.0.1: {}
|
| 6915 |
|
| 6916 |
+
camera-controls@3.1.2(three@0.180.0):
|
| 6917 |
+
dependencies:
|
| 6918 |
+
three: 0.180.0
|
| 6919 |
+
|
| 6920 |
caniuse-lite@1.0.30001769: {}
|
| 6921 |
|
| 6922 |
ccount@2.0.1: {}
|
|
|
|
| 6960 |
|
| 6961 |
chownr@1.1.4: {}
|
| 6962 |
|
| 6963 |
+
class-variance-authority@0.7.1:
|
| 6964 |
+
dependencies:
|
| 6965 |
+
clsx: 2.1.1
|
| 6966 |
+
|
| 6967 |
classcat@5.0.5: {}
|
| 6968 |
|
| 6969 |
+
classnames@2.5.1: {}
|
| 6970 |
+
|
| 6971 |
client-only@0.0.1: {}
|
| 6972 |
|
| 6973 |
clsx@2.1.1: {}
|
|
|
|
| 6992 |
|
| 6993 |
crelt@1.0.6: {}
|
| 6994 |
|
| 6995 |
+
cross-env@7.0.3:
|
| 6996 |
+
dependencies:
|
| 6997 |
+
cross-spawn: 7.0.6
|
| 6998 |
+
|
| 6999 |
cross-spawn@7.0.6:
|
| 7000 |
dependencies:
|
| 7001 |
path-key: 3.1.1
|
|
|
|
| 7029 |
dependencies:
|
| 7030 |
internmap: 2.0.3
|
| 7031 |
|
| 7032 |
+
d3-binarytree@1.0.2: {}
|
| 7033 |
+
|
| 7034 |
d3-color@3.1.0: {}
|
| 7035 |
|
| 7036 |
d3-dispatch@3.0.1: {}
|
|
|
|
| 7042 |
|
| 7043 |
d3-ease@3.0.1: {}
|
| 7044 |
|
| 7045 |
+
d3-force-3d@3.0.6:
|
| 7046 |
+
dependencies:
|
| 7047 |
+
d3-binarytree: 1.0.2
|
| 7048 |
+
d3-dispatch: 3.0.1
|
| 7049 |
+
d3-octree: 1.1.0
|
| 7050 |
+
d3-quadtree: 3.0.1
|
| 7051 |
+
d3-timer: 3.0.1
|
| 7052 |
+
|
| 7053 |
d3-format@3.1.2: {}
|
| 7054 |
|
| 7055 |
+
d3-hierarchy@3.1.2: {}
|
| 7056 |
+
|
| 7057 |
d3-interpolate@3.0.1:
|
| 7058 |
dependencies:
|
| 7059 |
d3-color: 3.1.0
|
| 7060 |
|
| 7061 |
+
d3-octree@1.1.0: {}
|
| 7062 |
+
|
| 7063 |
d3-path@3.1.0: {}
|
| 7064 |
|
| 7065 |
+
d3-quadtree@3.0.1: {}
|
| 7066 |
+
|
| 7067 |
d3-scale@4.0.2:
|
| 7068 |
dependencies:
|
| 7069 |
d3-array: 3.2.4
|
|
|
|
| 7174 |
|
| 7175 |
dequal@2.0.3: {}
|
| 7176 |
|
| 7177 |
+
detect-gpu@5.0.70:
|
| 7178 |
+
dependencies:
|
| 7179 |
+
webgl-constants: 1.1.1
|
| 7180 |
+
|
| 7181 |
detect-libc@2.1.2: {}
|
| 7182 |
|
| 7183 |
devlop@1.1.0:
|
|
|
|
| 7196 |
|
| 7197 |
dom-accessibility-api@0.6.3: {}
|
| 7198 |
|
| 7199 |
+
draco3d@1.5.7: {}
|
| 7200 |
+
|
| 7201 |
dunder-proto@1.0.1:
|
| 7202 |
dependencies:
|
| 7203 |
call-bind-apply-helpers: 1.0.2
|
|
|
|
| 7206 |
|
| 7207 |
electron-to-chromium@1.5.286: {}
|
| 7208 |
|
| 7209 |
+
ellipsize@0.6.2: {}
|
| 7210 |
+
|
| 7211 |
emoji-regex@9.2.2: {}
|
| 7212 |
|
| 7213 |
end-of-stream@1.4.5:
|
|
|
|
| 7568 |
|
| 7569 |
eventemitter3@5.0.4: {}
|
| 7570 |
|
| 7571 |
+
events@3.3.0: {}
|
| 7572 |
+
|
| 7573 |
eventsource-parser@3.0.6: {}
|
| 7574 |
|
| 7575 |
expand-template@2.0.3: {}
|
|
|
|
| 7614 |
optionalDependencies:
|
| 7615 |
picomatch: 4.0.3
|
| 7616 |
|
| 7617 |
+
fflate@0.6.10: {}
|
| 7618 |
+
|
| 7619 |
+
fflate@0.8.2: {}
|
| 7620 |
+
|
| 7621 |
file-entry-cache@8.0.0:
|
| 7622 |
dependencies:
|
| 7623 |
flat-cache: 4.0.1
|
|
|
|
| 7732 |
|
| 7733 |
globrex@0.1.2: {}
|
| 7734 |
|
| 7735 |
+
glsl-noise@0.0.0: {}
|
| 7736 |
+
|
| 7737 |
gopd@1.2.0: {}
|
| 7738 |
|
| 7739 |
+
graphology-indices@0.17.0(graphology-types@0.24.8):
|
| 7740 |
+
dependencies:
|
| 7741 |
+
graphology-types: 0.24.8
|
| 7742 |
+
graphology-utils: 2.5.2(graphology-types@0.24.8)
|
| 7743 |
+
mnemonist: 0.39.8
|
| 7744 |
+
|
| 7745 |
+
graphology-layout-forceatlas2@0.10.1(graphology-types@0.24.8):
|
| 7746 |
+
dependencies:
|
| 7747 |
+
graphology-types: 0.24.8
|
| 7748 |
+
graphology-utils: 2.5.2(graphology-types@0.24.8)
|
| 7749 |
+
|
| 7750 |
+
graphology-layout-noverlap@0.4.2(graphology-types@0.24.8):
|
| 7751 |
+
dependencies:
|
| 7752 |
+
graphology-types: 0.24.8
|
| 7753 |
+
graphology-utils: 2.5.2(graphology-types@0.24.8)
|
| 7754 |
+
|
| 7755 |
+
graphology-layout@0.6.1(graphology-types@0.24.8):
|
| 7756 |
+
dependencies:
|
| 7757 |
+
graphology-types: 0.24.8
|
| 7758 |
+
graphology-utils: 2.5.2(graphology-types@0.24.8)
|
| 7759 |
+
pandemonium: 2.4.1
|
| 7760 |
+
|
| 7761 |
+
graphology-metrics@2.4.0(graphology-types@0.24.8):
|
| 7762 |
+
dependencies:
|
| 7763 |
+
graphology-indices: 0.17.0(graphology-types@0.24.8)
|
| 7764 |
+
graphology-shortest-path: 2.1.0(graphology-types@0.24.8)
|
| 7765 |
+
graphology-types: 0.24.8
|
| 7766 |
+
graphology-utils: 2.5.2(graphology-types@0.24.8)
|
| 7767 |
+
mnemonist: 0.39.8
|
| 7768 |
+
pandemonium: 2.4.1
|
| 7769 |
+
|
| 7770 |
+
graphology-shortest-path@2.1.0(graphology-types@0.24.8):
|
| 7771 |
+
dependencies:
|
| 7772 |
+
'@yomguithereal/helpers': 1.1.1
|
| 7773 |
+
graphology-indices: 0.17.0(graphology-types@0.24.8)
|
| 7774 |
+
graphology-types: 0.24.8
|
| 7775 |
+
graphology-utils: 2.5.2(graphology-types@0.24.8)
|
| 7776 |
+
mnemonist: 0.39.8
|
| 7777 |
+
|
| 7778 |
+
graphology-types@0.24.8: {}
|
| 7779 |
+
|
| 7780 |
+
graphology-utils@2.5.2(graphology-types@0.24.8):
|
| 7781 |
+
dependencies:
|
| 7782 |
+
graphology-types: 0.24.8
|
| 7783 |
+
|
| 7784 |
+
graphology@0.26.0(graphology-types@0.24.8):
|
| 7785 |
+
dependencies:
|
| 7786 |
+
events: 3.3.0
|
| 7787 |
+
graphology-types: 0.24.8
|
| 7788 |
+
|
| 7789 |
guess-json-indent@3.0.1: {}
|
| 7790 |
|
| 7791 |
has-bigints@1.1.0: {}
|
|
|
|
| 7974 |
|
| 7975 |
highlightjs-curl@1.3.0: {}
|
| 7976 |
|
| 7977 |
+
hls.js@1.6.15: {}
|
| 7978 |
+
|
| 7979 |
+
hold-event@1.1.2: {}
|
| 7980 |
+
|
| 7981 |
hookable@6.0.1: {}
|
| 7982 |
|
| 7983 |
html-encoding-sniffer@4.0.0:
|
|
|
|
| 8018 |
|
| 8019 |
ignore@7.0.5: {}
|
| 8020 |
|
| 8021 |
+
immediate@3.0.6: {}
|
| 8022 |
+
|
| 8023 |
immer@10.2.0: {}
|
| 8024 |
|
| 8025 |
immer@11.1.3: {}
|
|
|
|
| 8148 |
|
| 8149 |
is-potential-custom-element-name@1.0.1: {}
|
| 8150 |
|
| 8151 |
+
is-promise@2.2.2: {}
|
| 8152 |
+
|
| 8153 |
is-regex@1.2.1:
|
| 8154 |
dependencies:
|
| 8155 |
call-bound: 1.0.4
|
|
|
|
| 8204 |
has-symbols: 1.1.0
|
| 8205 |
set-function-name: 2.0.2
|
| 8206 |
|
| 8207 |
+
its-fine@2.0.0(@types/react@19.2.13)(react@19.2.4):
|
| 8208 |
+
dependencies:
|
| 8209 |
+
'@types/react-reconciler': 0.28.9(@types/react@19.2.13)
|
| 8210 |
+
react: 19.2.4
|
| 8211 |
+
transitivePeerDependencies:
|
| 8212 |
+
- '@types/react'
|
| 8213 |
+
|
| 8214 |
jiti@1.21.7: {}
|
| 8215 |
|
| 8216 |
joycon@3.1.1: {}
|
|
|
|
| 8296 |
prelude-ls: 1.2.1
|
| 8297 |
type-check: 0.4.0
|
| 8298 |
|
| 8299 |
+
lie@3.3.0:
|
| 8300 |
+
dependencies:
|
| 8301 |
+
immediate: 3.0.6
|
| 8302 |
+
|
| 8303 |
lilconfig@3.1.3: {}
|
| 8304 |
|
| 8305 |
lines-and-columns@1.2.4: {}
|
|
|
|
| 8332 |
|
| 8333 |
lz-string@1.5.0: {}
|
| 8334 |
|
| 8335 |
+
maath@0.10.8(@types/three@0.183.1)(three@0.180.0):
|
| 8336 |
+
dependencies:
|
| 8337 |
+
'@types/three': 0.183.1
|
| 8338 |
+
three: 0.180.0
|
| 8339 |
+
|
| 8340 |
magic-string@0.30.21:
|
| 8341 |
dependencies:
|
| 8342 |
'@jridgewell/sourcemap-codec': 1.5.5
|
|
|
|
| 8506 |
|
| 8507 |
merge2@1.4.1: {}
|
| 8508 |
|
| 8509 |
+
meshline@3.3.1(three@0.180.0):
|
| 8510 |
+
dependencies:
|
| 8511 |
+
three: 0.180.0
|
| 8512 |
+
|
| 8513 |
+
meshoptimizer@1.0.1: {}
|
| 8514 |
+
|
| 8515 |
microdiff@1.5.0: {}
|
| 8516 |
|
| 8517 |
micromark-core-commonmark@2.0.3:
|
|
|
|
| 8726 |
|
| 8727 |
mkdirp-classic@0.5.3: {}
|
| 8728 |
|
| 8729 |
+
mnemonist@0.39.8:
|
| 8730 |
+
dependencies:
|
| 8731 |
+
obliterator: 2.0.5
|
| 8732 |
+
|
| 8733 |
ms@2.1.3: {}
|
| 8734 |
|
| 8735 |
mz@2.7.0:
|
|
|
|
| 8837 |
define-properties: 1.2.1
|
| 8838 |
es-object-atoms: 1.1.1
|
| 8839 |
|
| 8840 |
+
obliterator@2.0.5: {}
|
| 8841 |
+
|
| 8842 |
on-exit-leak-free@2.1.2: {}
|
| 8843 |
|
| 8844 |
once@1.4.0:
|
|
|
|
| 8874 |
|
| 8875 |
p-timeout@6.1.4: {}
|
| 8876 |
|
| 8877 |
+
pandemonium@2.4.1:
|
| 8878 |
+
dependencies:
|
| 8879 |
+
mnemonist: 0.39.8
|
| 8880 |
+
|
| 8881 |
parent-module@1.0.1:
|
| 8882 |
dependencies:
|
| 8883 |
callsites: 3.1.0
|
|
|
|
| 9010 |
picocolors: 1.1.1
|
| 9011 |
source-map-js: 1.2.1
|
| 9012 |
|
| 9013 |
+
potpack@1.0.2: {}
|
| 9014 |
+
|
| 9015 |
prebuild-install@7.1.3:
|
| 9016 |
dependencies:
|
| 9017 |
detect-libc: 2.1.2
|
|
|
|
| 9043 |
|
| 9044 |
process-warning@5.0.0: {}
|
| 9045 |
|
| 9046 |
+
promise-worker-transferable@1.0.4:
|
| 9047 |
+
dependencies:
|
| 9048 |
+
is-promise: 2.2.2
|
| 9049 |
+
lie: 3.3.0
|
| 9050 |
+
|
| 9051 |
prop-types@15.8.1:
|
| 9052 |
dependencies:
|
| 9053 |
loose-envify: 1.4.0
|
|
|
|
| 9129 |
|
| 9130 |
react-refresh@0.17.0: {}
|
| 9131 |
|
| 9132 |
+
react-use-measure@2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
| 9133 |
+
dependencies:
|
| 9134 |
+
react: 19.2.4
|
| 9135 |
+
optionalDependencies:
|
| 9136 |
+
react-dom: 19.2.4(react@19.2.4)
|
| 9137 |
+
|
| 9138 |
react@19.2.4: {}
|
| 9139 |
|
| 9140 |
reactflow@11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
|
|
|
| 9165 |
dependencies:
|
| 9166 |
picomatch: 2.3.1
|
| 9167 |
|
| 9168 |
+
reagraph@4.30.8(@types/react@19.2.13)(@types/three@0.183.1)(graphology-types@0.24.8)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)):
|
| 9169 |
+
dependencies:
|
| 9170 |
+
'@react-spring/three': 10.0.3(@react-three/fiber@9.5.0(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.180.0))(react@19.2.4)(three@0.180.0)
|
| 9171 |
+
'@react-three/drei': 10.7.7(@react-three/fiber@9.5.0(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.180.0))(@types/react@19.2.13)(@types/three@0.183.1)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.180.0)
|
| 9172 |
+
'@react-three/fiber': 9.5.0(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.180.0)
|
| 9173 |
+
'@use-gesture/react': 10.3.1(react@19.2.4)
|
| 9174 |
+
camera-controls: 3.1.2(three@0.180.0)
|
| 9175 |
+
classnames: 2.5.1
|
| 9176 |
+
d3-array: 3.2.4
|
| 9177 |
+
d3-force-3d: 3.0.6
|
| 9178 |
+
d3-hierarchy: 3.1.2
|
| 9179 |
+
d3-scale: 4.0.2
|
| 9180 |
+
ellipsize: 0.6.2
|
| 9181 |
+
graphology: 0.26.0(graphology-types@0.24.8)
|
| 9182 |
+
graphology-layout: 0.6.1(graphology-types@0.24.8)
|
| 9183 |
+
graphology-layout-forceatlas2: 0.10.1(graphology-types@0.24.8)
|
| 9184 |
+
graphology-layout-noverlap: 0.4.2(graphology-types@0.24.8)
|
| 9185 |
+
graphology-metrics: 2.4.0(graphology-types@0.24.8)
|
| 9186 |
+
graphology-shortest-path: 2.1.0(graphology-types@0.24.8)
|
| 9187 |
+
hold-event: 1.1.2
|
| 9188 |
+
react: 19.2.4
|
| 9189 |
+
react-dom: 19.2.4(react@19.2.4)
|
| 9190 |
+
three: 0.180.0
|
| 9191 |
+
three-stdlib: 2.36.1(three@0.180.0)
|
| 9192 |
+
zustand: 5.0.8(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
|
| 9193 |
+
transitivePeerDependencies:
|
| 9194 |
+
- '@types/react'
|
| 9195 |
+
- '@types/three'
|
| 9196 |
+
- expo
|
| 9197 |
+
- expo-asset
|
| 9198 |
+
- expo-file-system
|
| 9199 |
+
- expo-gl
|
| 9200 |
+
- graphology-types
|
| 9201 |
+
- immer
|
| 9202 |
+
- react-native
|
| 9203 |
+
- use-sync-external-store
|
| 9204 |
+
|
| 9205 |
real-require@0.2.0: {}
|
| 9206 |
|
| 9207 |
recharts@3.7.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1):
|
|
|
|
| 9538 |
|
| 9539 |
stackback@0.0.2: {}
|
| 9540 |
|
| 9541 |
+
stats-gl@2.4.2(@types/three@0.183.1)(three@0.180.0):
|
| 9542 |
+
dependencies:
|
| 9543 |
+
'@types/three': 0.183.1
|
| 9544 |
+
three: 0.180.0
|
| 9545 |
+
|
| 9546 |
+
stats.js@0.17.0: {}
|
| 9547 |
+
|
| 9548 |
std-env@3.10.0: {}
|
| 9549 |
|
| 9550 |
stop-iteration-iterator@1.1.0:
|
|
|
|
| 9673 |
|
| 9674 |
supports-preserve-symlinks-flag@1.0.0: {}
|
| 9675 |
|
| 9676 |
+
suspend-react@0.1.3(react@19.2.4):
|
| 9677 |
+
dependencies:
|
| 9678 |
+
react: 19.2.4
|
| 9679 |
+
|
| 9680 |
swrv@1.1.0(vue@3.5.29(typescript@5.9.3)):
|
| 9681 |
dependencies:
|
| 9682 |
vue: 3.5.29(typescript@5.9.3)
|
|
|
|
| 9744 |
dependencies:
|
| 9745 |
real-require: 0.2.0
|
| 9746 |
|
| 9747 |
+
three-mesh-bvh@0.8.3(three@0.180.0):
|
| 9748 |
+
dependencies:
|
| 9749 |
+
three: 0.180.0
|
| 9750 |
+
|
| 9751 |
+
three-stdlib@2.36.1(three@0.180.0):
|
| 9752 |
+
dependencies:
|
| 9753 |
+
'@types/draco3d': 1.4.10
|
| 9754 |
+
'@types/offscreencanvas': 2019.7.3
|
| 9755 |
+
'@types/webxr': 0.5.24
|
| 9756 |
+
draco3d: 1.5.7
|
| 9757 |
+
fflate: 0.6.10
|
| 9758 |
+
potpack: 1.0.2
|
| 9759 |
+
three: 0.180.0
|
| 9760 |
+
|
| 9761 |
+
three@0.180.0: {}
|
| 9762 |
+
|
| 9763 |
time-span@5.1.0:
|
| 9764 |
dependencies:
|
| 9765 |
convert-hrtime: 5.0.0
|
|
|
|
| 9801 |
|
| 9802 |
trim-lines@3.0.1: {}
|
| 9803 |
|
| 9804 |
+
troika-three-text@0.52.4(three@0.180.0):
|
| 9805 |
+
dependencies:
|
| 9806 |
+
bidi-js: 1.0.3
|
| 9807 |
+
three: 0.180.0
|
| 9808 |
+
troika-three-utils: 0.52.4(three@0.180.0)
|
| 9809 |
+
troika-worker-utils: 0.52.0
|
| 9810 |
+
webgl-sdf-generator: 1.1.1
|
| 9811 |
+
|
| 9812 |
+
troika-three-utils@0.52.4(three@0.180.0):
|
| 9813 |
+
dependencies:
|
| 9814 |
+
three: 0.180.0
|
| 9815 |
+
|
| 9816 |
+
troika-worker-utils@0.52.0: {}
|
| 9817 |
+
|
| 9818 |
trough@2.2.0: {}
|
| 9819 |
|
| 9820 |
truncate-json@3.0.1:
|
|
|
|
| 9848 |
dependencies:
|
| 9849 |
safe-buffer: 5.2.1
|
| 9850 |
|
| 9851 |
+
tunnel-rat@0.1.2(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4):
|
| 9852 |
+
dependencies:
|
| 9853 |
+
zustand: 4.5.7(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4)
|
| 9854 |
+
transitivePeerDependencies:
|
| 9855 |
+
- '@types/react'
|
| 9856 |
+
- immer
|
| 9857 |
+
- react
|
| 9858 |
+
|
| 9859 |
type-check@0.4.0:
|
| 9860 |
dependencies:
|
| 9861 |
prelude-ls: 1.2.1
|
|
|
|
| 10003 |
|
| 10004 |
util-deprecate@1.0.2: {}
|
| 10005 |
|
| 10006 |
+
utility-types@3.11.0: {}
|
| 10007 |
+
|
| 10008 |
vfile-location@5.0.3:
|
| 10009 |
dependencies:
|
| 10010 |
'@types/unist': 3.0.3
|
|
|
|
| 10144 |
|
| 10145 |
web-worker@1.5.0: {}
|
| 10146 |
|
| 10147 |
+
webgl-constants@1.1.1: {}
|
| 10148 |
+
|
| 10149 |
+
webgl-sdf-generator@1.1.1: {}
|
| 10150 |
+
|
| 10151 |
webidl-conversions@7.0.0: {}
|
| 10152 |
|
| 10153 |
whatwg-encoding@3.1.1:
|
|
|
|
| 10248 |
react: 19.2.4
|
| 10249 |
use-sync-external-store: 1.6.0(react@19.2.4)
|
| 10250 |
|
| 10251 |
+
zustand@5.0.8(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)):
|
| 10252 |
+
optionalDependencies:
|
| 10253 |
+
'@types/react': 19.2.13
|
| 10254 |
+
immer: 11.1.3
|
| 10255 |
+
react: 19.2.4
|
| 10256 |
+
use-sync-external-store: 1.6.0(react@19.2.4)
|
| 10257 |
+
|
| 10258 |
zwitch@2.0.4: {}
|
|
Git LFS Details
|
|
Git LFS Details
|
|
Git LFS Details
|
|
Git LFS Details
|
|
Git LFS Details
|
|
Git LFS Details
|
|
Git LFS Details
|
|
Git LFS Details
|
|
Git LFS Details
|
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
const REQUIRED_NODE_MAJOR = 22
|
| 4 |
+
|
| 5 |
+
const current = process.versions.node
|
| 6 |
+
const currentMajor = Number.parseInt(current.split('.')[0] || '', 10)
|
| 7 |
+
|
| 8 |
+
if (currentMajor !== REQUIRED_NODE_MAJOR) {
|
| 9 |
+
console.error(
|
| 10 |
+
[
|
| 11 |
+
`error: Mission Control requires Node ${REQUIRED_NODE_MAJOR}.x, but found ${current}.`,
|
| 12 |
+
'use `nvm use 22` (or your version manager equivalent) before installing, building, or starting the app.',
|
| 13 |
+
].join('\n')
|
| 14 |
+
)
|
| 15 |
+
process.exit(1)
|
| 16 |
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -euo pipefail
|
| 4 |
+
|
| 5 |
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 6 |
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
| 7 |
+
|
| 8 |
+
BRANCH="${BRANCH:-$(git -C "$PROJECT_ROOT" branch --show-current)}"
|
| 9 |
+
PORT="${PORT:-3000}"
|
| 10 |
+
LISTEN_HOST="${MC_HOSTNAME:-0.0.0.0}"
|
| 11 |
+
LOG_PATH="${LOG_PATH:-/tmp/mc.log}"
|
| 12 |
+
VERIFY_HOST="${VERIFY_HOST:-127.0.0.1}"
|
| 13 |
+
PID_FILE="${PID_FILE:-$PROJECT_ROOT/.next/standalone/server.pid}"
|
| 14 |
+
SOURCE_DATA_DIR="$PROJECT_ROOT/.data"
|
| 15 |
+
BUILD_DATA_DIR="$PROJECT_ROOT/.next/build-runtime"
|
| 16 |
+
NODE_VERSION_FILE="$PROJECT_ROOT/.nvmrc"
|
| 17 |
+
|
| 18 |
+
use_project_node() {
|
| 19 |
+
if [[ ! -f "$NODE_VERSION_FILE" ]]; then
|
| 20 |
+
return
|
| 21 |
+
fi
|
| 22 |
+
|
| 23 |
+
if [[ -z "${NVM_DIR:-}" ]]; then
|
| 24 |
+
export NVM_DIR="$HOME/.nvm"
|
| 25 |
+
fi
|
| 26 |
+
|
| 27 |
+
if [[ -s "$NVM_DIR/nvm.sh" ]]; then
|
| 28 |
+
# shellcheck disable=SC1090
|
| 29 |
+
source "$NVM_DIR/nvm.sh"
|
| 30 |
+
nvm use >/dev/null
|
| 31 |
+
fi
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
list_listener_pids() {
|
| 35 |
+
local combined=""
|
| 36 |
+
|
| 37 |
+
if command -v lsof >/dev/null 2>&1; then
|
| 38 |
+
combined+="$(
|
| 39 |
+
lsof -tiTCP:"$PORT" -sTCP:LISTEN 2>/dev/null || true
|
| 40 |
+
)"$'\n'
|
| 41 |
+
fi
|
| 42 |
+
|
| 43 |
+
if command -v ss >/dev/null 2>&1; then
|
| 44 |
+
combined+="$(
|
| 45 |
+
ss -ltnp 2>/dev/null | awk -v port=":$PORT" '
|
| 46 |
+
index($4, port) || index($5, port) {
|
| 47 |
+
if (match($0, /pid=[0-9]+/)) {
|
| 48 |
+
print substr($0, RSTART + 4, RLENGTH - 4)
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
'
|
| 52 |
+
)"$'\n'
|
| 53 |
+
fi
|
| 54 |
+
|
| 55 |
+
printf '%s\n' "$combined" | awk -v port="$PORT" '
|
| 56 |
+
/^[0-9]+$/ {
|
| 57 |
+
seen[$0] = 1
|
| 58 |
+
}
|
| 59 |
+
END {
|
| 60 |
+
for (pid in seen) {
|
| 61 |
+
print pid
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
' | sort -u
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
stop_pid() {
|
| 68 |
+
local pid="$1"
|
| 69 |
+
local label="$2"
|
| 70 |
+
|
| 71 |
+
if [[ -z "$pid" ]] || ! kill -0 "$pid" 2>/dev/null; then
|
| 72 |
+
return
|
| 73 |
+
fi
|
| 74 |
+
|
| 75 |
+
echo "==> stopping $label (pid=$pid)"
|
| 76 |
+
kill "$pid" 2>/dev/null || true
|
| 77 |
+
|
| 78 |
+
for _ in $(seq 1 10); do
|
| 79 |
+
if ! kill -0 "$pid" 2>/dev/null; then
|
| 80 |
+
return
|
| 81 |
+
fi
|
| 82 |
+
sleep 1
|
| 83 |
+
done
|
| 84 |
+
|
| 85 |
+
echo "==> force stopping $label (pid=$pid)"
|
| 86 |
+
kill -9 "$pid" 2>/dev/null || true
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
stop_existing_server() {
|
| 90 |
+
local -a candidate_pids=()
|
| 91 |
+
|
| 92 |
+
if [[ -f "$PID_FILE" ]]; then
|
| 93 |
+
candidate_pids+=("$(cat "$PID_FILE" 2>/dev/null || true)")
|
| 94 |
+
fi
|
| 95 |
+
|
| 96 |
+
while IFS= read -r pid; do
|
| 97 |
+
candidate_pids+=("$pid")
|
| 98 |
+
done < <(list_listener_pids)
|
| 99 |
+
|
| 100 |
+
if command -v pgrep >/dev/null 2>&1; then
|
| 101 |
+
while IFS= read -r pid; do
|
| 102 |
+
candidate_pids+=("$pid")
|
| 103 |
+
done < <(pgrep -f "$PROJECT_ROOT/.next/standalone/server.js" || true)
|
| 104 |
+
fi
|
| 105 |
+
|
| 106 |
+
if [[ ${#candidate_pids[@]} -eq 0 ]]; then
|
| 107 |
+
return
|
| 108 |
+
fi
|
| 109 |
+
|
| 110 |
+
declare -A seen=()
|
| 111 |
+
for pid in "${candidate_pids[@]}"; do
|
| 112 |
+
[[ -z "$pid" ]] && continue
|
| 113 |
+
[[ -n "${seen[$pid]:-}" ]] && continue
|
| 114 |
+
seen[$pid]=1
|
| 115 |
+
stop_pid "$pid" "standalone server"
|
| 116 |
+
done
|
| 117 |
+
|
| 118 |
+
for _ in $(seq 1 10); do
|
| 119 |
+
if [[ -z "$(list_listener_pids | head -n1)" ]]; then
|
| 120 |
+
rm -f "$PID_FILE"
|
| 121 |
+
return
|
| 122 |
+
fi
|
| 123 |
+
sleep 1
|
| 124 |
+
done
|
| 125 |
+
|
| 126 |
+
echo "error: port $PORT is still in use after stopping existing server" >&2
|
| 127 |
+
exit 1
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
load_env() {
|
| 131 |
+
set -a
|
| 132 |
+
if [[ -f .env ]]; then
|
| 133 |
+
# shellcheck disable=SC1091
|
| 134 |
+
source .env
|
| 135 |
+
fi
|
| 136 |
+
if [[ -f .env.local ]]; then
|
| 137 |
+
# shellcheck disable=SC1091
|
| 138 |
+
source .env.local
|
| 139 |
+
fi
|
| 140 |
+
set +a
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
migrate_runtime_data_dir() {
|
| 144 |
+
local target_data_dir="${MISSION_CONTROL_DATA_DIR:-$SOURCE_DATA_DIR}"
|
| 145 |
+
|
| 146 |
+
if [[ "$target_data_dir" == "$SOURCE_DATA_DIR" ]]; then
|
| 147 |
+
return
|
| 148 |
+
fi
|
| 149 |
+
|
| 150 |
+
mkdir -p "$target_data_dir"
|
| 151 |
+
|
| 152 |
+
local source_db="$SOURCE_DATA_DIR/mission-control.db"
|
| 153 |
+
local target_db="$target_data_dir/mission-control.db"
|
| 154 |
+
|
| 155 |
+
if [[ -s "$target_db" || ! -s "$source_db" ]]; then
|
| 156 |
+
return
|
| 157 |
+
fi
|
| 158 |
+
|
| 159 |
+
echo "==> migrating runtime data to $target_data_dir"
|
| 160 |
+
if command -v sqlite3 >/dev/null 2>&1; then
|
| 161 |
+
local target_db_tmp="$target_db.tmp"
|
| 162 |
+
rm -f "$target_db_tmp"
|
| 163 |
+
sqlite3 "$source_db" ".backup '$target_db_tmp'"
|
| 164 |
+
mv "$target_db_tmp" "$target_db"
|
| 165 |
+
|
| 166 |
+
if [[ -f "$SOURCE_DATA_DIR/mission-control-tokens.json" ]]; then
|
| 167 |
+
cp "$SOURCE_DATA_DIR/mission-control-tokens.json" "$target_data_dir/mission-control-tokens.json"
|
| 168 |
+
fi
|
| 169 |
+
if [[ -d "$SOURCE_DATA_DIR/backups" ]]; then
|
| 170 |
+
rsync -a "$SOURCE_DATA_DIR/backups"/ "$target_data_dir/backups"/
|
| 171 |
+
fi
|
| 172 |
+
else
|
| 173 |
+
rsync -a \
|
| 174 |
+
--exclude 'mission-control.db-shm' \
|
| 175 |
+
--exclude 'mission-control.db-wal' \
|
| 176 |
+
--exclude '*.db-shm' \
|
| 177 |
+
--exclude '*.db-wal' \
|
| 178 |
+
"$SOURCE_DATA_DIR"/ "$target_data_dir"/
|
| 179 |
+
fi
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
cd "$PROJECT_ROOT"
|
| 183 |
+
use_project_node
|
| 184 |
+
|
| 185 |
+
echo "==> fetching branch $BRANCH"
|
| 186 |
+
git fetch origin "$BRANCH"
|
| 187 |
+
git merge --ff-only FETCH_HEAD
|
| 188 |
+
|
| 189 |
+
load_env
|
| 190 |
+
migrate_runtime_data_dir
|
| 191 |
+
|
| 192 |
+
echo "==> stopping existing standalone server before rebuild"
|
| 193 |
+
stop_existing_server
|
| 194 |
+
|
| 195 |
+
echo "==> installing dependencies"
|
| 196 |
+
pnpm install --frozen-lockfile
|
| 197 |
+
|
| 198 |
+
echo "==> rebuilding standalone bundle"
|
| 199 |
+
rm -rf .next
|
| 200 |
+
mkdir -p "$BUILD_DATA_DIR"
|
| 201 |
+
MISSION_CONTROL_DATA_DIR="$BUILD_DATA_DIR" \
|
| 202 |
+
MISSION_CONTROL_DB_PATH="$BUILD_DATA_DIR/mission-control.db" \
|
| 203 |
+
MISSION_CONTROL_TOKENS_PATH="$BUILD_DATA_DIR/mission-control-tokens.json" \
|
| 204 |
+
pnpm build
|
| 205 |
+
|
| 206 |
+
echo "==> starting standalone server"
|
| 207 |
+
load_env
|
| 208 |
+
|
| 209 |
+
PORT="$PORT" HOSTNAME="$LISTEN_HOST" nohup bash "$PROJECT_ROOT/scripts/start-standalone.sh" >"$LOG_PATH" 2>&1 &
|
| 210 |
+
new_pid=$!
|
| 211 |
+
echo "$new_pid" > "$PID_FILE"
|
| 212 |
+
|
| 213 |
+
echo "==> verifying process and static assets"
|
| 214 |
+
for _ in $(seq 1 20); do
|
| 215 |
+
if curl -fsS "http://$VERIFY_HOST:$PORT/login" >/dev/null 2>&1; then
|
| 216 |
+
break
|
| 217 |
+
fi
|
| 218 |
+
sleep 1
|
| 219 |
+
done
|
| 220 |
+
|
| 221 |
+
login_html="$(curl -fsS "http://$VERIFY_HOST:$PORT/login")"
|
| 222 |
+
css_path="$(printf '%s\n' "$login_html" | sed -n 's|.*\(/_next/static/chunks/[^"]*\.css\).*|\1|p' | sed -n '1p')"
|
| 223 |
+
if [[ -z "${css_path:-}" ]]; then
|
| 224 |
+
echo "error: no css asset found in rendered login HTML" >&2
|
| 225 |
+
exit 1
|
| 226 |
+
fi
|
| 227 |
+
|
| 228 |
+
listener_pid="$(list_listener_pids | head -n1)"
|
| 229 |
+
if [[ -z "${listener_pid:-}" ]]; then
|
| 230 |
+
echo "error: no listener detected on port $PORT after startup" >&2
|
| 231 |
+
exit 1
|
| 232 |
+
fi
|
| 233 |
+
if [[ "$listener_pid" != "$new_pid" ]]; then
|
| 234 |
+
echo "error: port $PORT is owned by pid=$listener_pid, expected new pid=$new_pid" >&2
|
| 235 |
+
exit 1
|
| 236 |
+
fi
|
| 237 |
+
|
| 238 |
+
css_disk_path="$PROJECT_ROOT/.next/standalone/.next${css_path#/_next}"
|
| 239 |
+
if [[ ! -f "$css_disk_path" ]]; then
|
| 240 |
+
echo "error: rendered css asset missing on disk: $css_disk_path" >&2
|
| 241 |
+
exit 1
|
| 242 |
+
fi
|
| 243 |
+
|
| 244 |
+
content_type="$(curl -fsSI "http://$VERIFY_HOST:$PORT$css_path" | awk 'BEGIN{IGNORECASE=1} /^content-type:/ {print $2}' | tr -d '\r')"
|
| 245 |
+
if [[ "${content_type:-}" != text/css* ]]; then
|
| 246 |
+
echo "error: css asset served with unexpected content-type: ${content_type:-missing}" >&2
|
| 247 |
+
exit 1
|
| 248 |
+
fi
|
| 249 |
+
|
| 250 |
+
echo "==> deployed commit $(git rev-parse --short HEAD)"
|
| 251 |
+
echo " pid=$new_pid port=$PORT css=$css_path"
|
|
@@ -1,9 +1,30 @@
|
|
| 1 |
#!/usr/bin/env node
|
| 2 |
import { spawn } from 'node:child_process'
|
| 3 |
import fs from 'node:fs'
|
|
|
|
| 4 |
import path from 'node:path'
|
| 5 |
import process from 'node:process'
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
const modeArg = process.argv.find((arg) => arg.startsWith('--mode='))
|
| 8 |
const mode = modeArg ? modeArg.split('=')[1] : 'local'
|
| 9 |
if (mode !== 'local' && mode !== 'gateway') {
|
|
@@ -16,6 +37,7 @@ const fixtureSource = path.join(repoRoot, 'tests', 'fixtures', 'openclaw')
|
|
| 16 |
const runtimeRoot = path.join(repoRoot, '.tmp', 'e2e-openclaw', mode)
|
| 17 |
const dataDir = path.join(runtimeRoot, 'data')
|
| 18 |
const mockBinDir = path.join(repoRoot, 'scripts', 'e2e-openclaw', 'bin')
|
|
|
|
| 19 |
|
| 20 |
fs.rmSync(runtimeRoot, { recursive: true, force: true })
|
| 21 |
fs.mkdirSync(runtimeRoot, { recursive: true })
|
|
@@ -23,13 +45,14 @@ fs.mkdirSync(dataDir, { recursive: true })
|
|
| 23 |
fs.cpSync(fixtureSource, runtimeRoot, { recursive: true })
|
| 24 |
|
| 25 |
const gatewayHost = '127.0.0.1'
|
| 26 |
-
const gatewayPort =
|
| 27 |
|
| 28 |
const baseEnv = {
|
| 29 |
...process.env,
|
| 30 |
API_KEY: process.env.API_KEY || 'test-api-key-e2e-12345',
|
| 31 |
AUTH_USER: process.env.AUTH_USER || 'admin',
|
| 32 |
AUTH_PASS: process.env.AUTH_PASS || 'admin',
|
|
|
|
| 33 |
MC_DISABLE_RATE_LIMIT: '1',
|
| 34 |
MISSION_CONTROL_DATA_DIR: dataDir,
|
| 35 |
MISSION_CONTROL_DB_PATH: path.join(dataDir, 'mission-control.db'),
|
|
@@ -39,11 +62,17 @@ const baseEnv = {
|
|
| 39 |
OPENCLAW_GATEWAY_PORT: gatewayPort,
|
| 40 |
OPENCLAW_BIN: path.join(mockBinDir, 'openclaw'),
|
| 41 |
CLAWDBOT_BIN: path.join(mockBinDir, 'clawdbot'),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
PATH: `${mockBinDir}:${process.env.PATH || ''}`,
|
| 43 |
E2E_GATEWAY_EXPECTED: mode === 'gateway' ? '1' : '0',
|
| 44 |
}
|
| 45 |
|
| 46 |
const children = []
|
|
|
|
| 47 |
|
| 48 |
if (mode === 'gateway') {
|
| 49 |
const gw = spawn('node', ['scripts/e2e-openclaw/mock-gateway.mjs'], {
|
|
@@ -51,11 +80,24 @@ if (mode === 'gateway') {
|
|
| 51 |
env: baseEnv,
|
| 52 |
stdio: 'inherit',
|
| 53 |
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
children.push(gw)
|
| 55 |
}
|
| 56 |
|
| 57 |
const standaloneServerPath = path.join(repoRoot, '.next', 'standalone', 'server.js')
|
| 58 |
-
|
| 59 |
? spawn('node', [standaloneServerPath], {
|
| 60 |
cwd: repoRoot,
|
| 61 |
env: {
|
|
|
|
| 1 |
#!/usr/bin/env node
|
| 2 |
import { spawn } from 'node:child_process'
|
| 3 |
import fs from 'node:fs'
|
| 4 |
+
import net from 'node:net'
|
| 5 |
import path from 'node:path'
|
| 6 |
import process from 'node:process'
|
| 7 |
|
| 8 |
+
async function findAvailablePort(host = '127.0.0.1') {
|
| 9 |
+
return await new Promise((resolve, reject) => {
|
| 10 |
+
const server = net.createServer()
|
| 11 |
+
server.unref()
|
| 12 |
+
server.on('error', reject)
|
| 13 |
+
server.listen(0, host, () => {
|
| 14 |
+
const address = server.address()
|
| 15 |
+
if (!address || typeof address === 'string') {
|
| 16 |
+
server.close(() => reject(new Error('failed to resolve dynamic port')))
|
| 17 |
+
return
|
| 18 |
+
}
|
| 19 |
+
const { port } = address
|
| 20 |
+
server.close((err) => {
|
| 21 |
+
if (err) reject(err)
|
| 22 |
+
else resolve(port)
|
| 23 |
+
})
|
| 24 |
+
})
|
| 25 |
+
})
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
const modeArg = process.argv.find((arg) => arg.startsWith('--mode='))
|
| 29 |
const mode = modeArg ? modeArg.split('=')[1] : 'local'
|
| 30 |
if (mode !== 'local' && mode !== 'gateway') {
|
|
|
|
| 37 |
const runtimeRoot = path.join(repoRoot, '.tmp', 'e2e-openclaw', mode)
|
| 38 |
const dataDir = path.join(runtimeRoot, 'data')
|
| 39 |
const mockBinDir = path.join(repoRoot, 'scripts', 'e2e-openclaw', 'bin')
|
| 40 |
+
const skillsRoot = path.join(runtimeRoot, 'skills')
|
| 41 |
|
| 42 |
fs.rmSync(runtimeRoot, { recursive: true, force: true })
|
| 43 |
fs.mkdirSync(runtimeRoot, { recursive: true })
|
|
|
|
| 45 |
fs.cpSync(fixtureSource, runtimeRoot, { recursive: true })
|
| 46 |
|
| 47 |
const gatewayHost = '127.0.0.1'
|
| 48 |
+
const gatewayPort = String(await findAvailablePort(gatewayHost))
|
| 49 |
|
| 50 |
const baseEnv = {
|
| 51 |
...process.env,
|
| 52 |
API_KEY: process.env.API_KEY || 'test-api-key-e2e-12345',
|
| 53 |
AUTH_USER: process.env.AUTH_USER || 'admin',
|
| 54 |
AUTH_PASS: process.env.AUTH_PASS || 'admin',
|
| 55 |
+
MISSION_CONTROL_TEST_MODE: process.env.MISSION_CONTROL_TEST_MODE || '1',
|
| 56 |
MC_DISABLE_RATE_LIMIT: '1',
|
| 57 |
MISSION_CONTROL_DATA_DIR: dataDir,
|
| 58 |
MISSION_CONTROL_DB_PATH: path.join(dataDir, 'mission-control.db'),
|
|
|
|
| 62 |
OPENCLAW_GATEWAY_PORT: gatewayPort,
|
| 63 |
OPENCLAW_BIN: path.join(mockBinDir, 'openclaw'),
|
| 64 |
CLAWDBOT_BIN: path.join(mockBinDir, 'clawdbot'),
|
| 65 |
+
MC_SKILLS_USER_AGENTS_DIR: path.join(skillsRoot, 'user-agents'),
|
| 66 |
+
MC_SKILLS_USER_CODEX_DIR: path.join(skillsRoot, 'user-codex'),
|
| 67 |
+
MC_SKILLS_PROJECT_AGENTS_DIR: path.join(skillsRoot, 'project-agents'),
|
| 68 |
+
MC_SKILLS_PROJECT_CODEX_DIR: path.join(skillsRoot, 'project-codex'),
|
| 69 |
+
MC_SKILLS_OPENCLAW_DIR: path.join(skillsRoot, 'openclaw'),
|
| 70 |
PATH: `${mockBinDir}:${process.env.PATH || ''}`,
|
| 71 |
E2E_GATEWAY_EXPECTED: mode === 'gateway' ? '1' : '0',
|
| 72 |
}
|
| 73 |
|
| 74 |
const children = []
|
| 75 |
+
let app = null
|
| 76 |
|
| 77 |
if (mode === 'gateway') {
|
| 78 |
const gw = spawn('node', ['scripts/e2e-openclaw/mock-gateway.mjs'], {
|
|
|
|
| 80 |
env: baseEnv,
|
| 81 |
stdio: 'inherit',
|
| 82 |
})
|
| 83 |
+
gw.on('error', (err) => {
|
| 84 |
+
process.stderr.write(`[openclaw-e2e] mock gateway failed to start: ${String(err)}\n`)
|
| 85 |
+
shutdown('SIGTERM')
|
| 86 |
+
process.exit(1)
|
| 87 |
+
})
|
| 88 |
+
gw.on('exit', (code, signal) => {
|
| 89 |
+
const exitCode = code ?? (signal ? 1 : 0)
|
| 90 |
+
if (exitCode !== 0) {
|
| 91 |
+
process.stderr.write(`[openclaw-e2e] mock gateway exited unexpectedly (code=${exitCode}, signal=${signal ?? 'none'})\n`)
|
| 92 |
+
shutdown('SIGTERM')
|
| 93 |
+
process.exit(exitCode)
|
| 94 |
+
}
|
| 95 |
+
})
|
| 96 |
children.push(gw)
|
| 97 |
}
|
| 98 |
|
| 99 |
const standaloneServerPath = path.join(repoRoot, '.next', 'standalone', 'server.js')
|
| 100 |
+
app = fs.existsSync(standaloneServerPath)
|
| 101 |
? spawn('node', [standaloneServerPath], {
|
| 102 |
cwd: repoRoot,
|
| 103 |
env: {
|
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Generate a secure .env file from .env.example with random secrets.
|
| 3 |
+
# Usage: bash scripts/generate-env.sh [output-path]
|
| 4 |
+
#
|
| 5 |
+
# If output-path is omitted, writes to .env in the project root.
|
| 6 |
+
# Will NOT overwrite an existing .env unless --force is passed.
|
| 7 |
+
|
| 8 |
+
set -euo pipefail
|
| 9 |
+
|
| 10 |
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 11 |
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
| 12 |
+
EXAMPLE_FILE="$PROJECT_ROOT/.env.example"
|
| 13 |
+
FORCE=false
|
| 14 |
+
OUTPUT=""
|
| 15 |
+
|
| 16 |
+
for arg in "$@"; do
|
| 17 |
+
case "$arg" in
|
| 18 |
+
--force) FORCE=true ;;
|
| 19 |
+
*) OUTPUT="$arg" ;;
|
| 20 |
+
esac
|
| 21 |
+
done
|
| 22 |
+
|
| 23 |
+
OUTPUT="${OUTPUT:-$PROJECT_ROOT/.env}"
|
| 24 |
+
|
| 25 |
+
if [[ -f "$OUTPUT" ]] && ! $FORCE; then
|
| 26 |
+
echo "Error: $OUTPUT already exists. Use --force to overwrite."
|
| 27 |
+
exit 1
|
| 28 |
+
fi
|
| 29 |
+
|
| 30 |
+
if [[ ! -f "$EXAMPLE_FILE" ]]; then
|
| 31 |
+
echo "Error: .env.example not found at $EXAMPLE_FILE"
|
| 32 |
+
exit 1
|
| 33 |
+
fi
|
| 34 |
+
|
| 35 |
+
# Generate cryptographically random values
|
| 36 |
+
generate_password() {
|
| 37 |
+
local len="${1:-24}"
|
| 38 |
+
# Use openssl if available, fallback to /dev/urandom
|
| 39 |
+
if command -v openssl &>/dev/null; then
|
| 40 |
+
openssl rand -base64 "$((len * 3 / 4 + 1))" | tr -dc 'A-Za-z0-9' | head -c "$len"
|
| 41 |
+
else
|
| 42 |
+
head -c "$((len * 2))" /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c "$len"
|
| 43 |
+
fi
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
generate_hex() {
|
| 47 |
+
local len="${1:-32}"
|
| 48 |
+
if command -v openssl &>/dev/null; then
|
| 49 |
+
openssl rand -hex "$((len / 2))"
|
| 50 |
+
else
|
| 51 |
+
head -c "$((len / 2))" /dev/urandom | od -An -tx1 | tr -d ' \n' | head -c "$len"
|
| 52 |
+
fi
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
AUTH_PASS="$(generate_password 24)"
|
| 56 |
+
API_KEY="$(generate_hex 32)"
|
| 57 |
+
AUTH_SECRET="$(generate_password 32)"
|
| 58 |
+
|
| 59 |
+
# Copy .env.example and replace default secrets
|
| 60 |
+
cp "$EXAMPLE_FILE" "$OUTPUT"
|
| 61 |
+
|
| 62 |
+
# Replace the insecure defaults with generated values
|
| 63 |
+
if [[ "$(uname)" == "Darwin" ]]; then
|
| 64 |
+
sed -i '' "s|^AUTH_PASS=.*|AUTH_PASS=$AUTH_PASS|" "$OUTPUT"
|
| 65 |
+
sed -i '' "s|^API_KEY=.*|API_KEY=$API_KEY|" "$OUTPUT"
|
| 66 |
+
sed -i '' "s|^AUTH_SECRET=.*|AUTH_SECRET=$AUTH_SECRET|" "$OUTPUT"
|
| 67 |
+
else
|
| 68 |
+
sed -i "s|^AUTH_PASS=.*|AUTH_PASS=$AUTH_PASS|" "$OUTPUT"
|
| 69 |
+
sed -i "s|^API_KEY=.*|API_KEY=$API_KEY|" "$OUTPUT"
|
| 70 |
+
sed -i "s|^AUTH_SECRET=.*|AUTH_SECRET=$AUTH_SECRET|" "$OUTPUT"
|
| 71 |
+
fi
|
| 72 |
+
|
| 73 |
+
# Lock down permissions
|
| 74 |
+
chmod 600 "$OUTPUT"
|
| 75 |
+
|
| 76 |
+
echo "Generated secure .env at $OUTPUT"
|
| 77 |
+
echo " AUTH_USER: admin"
|
| 78 |
+
echo " AUTH_PASS: $AUTH_PASS"
|
| 79 |
+
echo " API_KEY: $API_KEY"
|
| 80 |
+
echo ""
|
| 81 |
+
echo "Save these credentials — they are not stored elsewhere."
|
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Mission Control Security Audit
|
| 3 |
+
# Run: bash scripts/security-audit.sh [--env-file .env]
|
| 4 |
+
|
| 5 |
+
set -euo pipefail
|
| 6 |
+
|
| 7 |
+
SCORE=0
|
| 8 |
+
MAX_SCORE=0
|
| 9 |
+
ISSUES=()
|
| 10 |
+
|
| 11 |
+
pass() { echo " [PASS] $1"; ((SCORE++)); ((MAX_SCORE++)); }
|
| 12 |
+
fail() { echo " [FAIL] $1"; ISSUES+=("$1"); ((MAX_SCORE++)); }
|
| 13 |
+
warn() { echo " [WARN] $1"; ((MAX_SCORE++)); }
|
| 14 |
+
info() { echo " [INFO] $1"; }
|
| 15 |
+
|
| 16 |
+
# Load .env if exists
|
| 17 |
+
ENV_FILE="${1:-.env}"
|
| 18 |
+
if [[ -f "$ENV_FILE" ]]; then
|
| 19 |
+
while IFS='=' read -r key value; do
|
| 20 |
+
[[ "$key" =~ ^#.*$ ]] && continue
|
| 21 |
+
[[ -z "$key" ]] && continue
|
| 22 |
+
declare "$key=$value" 2>/dev/null || true
|
| 23 |
+
done < "$ENV_FILE"
|
| 24 |
+
fi
|
| 25 |
+
|
| 26 |
+
echo "=== Mission Control Security Audit ==="
|
| 27 |
+
echo ""
|
| 28 |
+
|
| 29 |
+
# 1. .env file permissions
|
| 30 |
+
echo "--- File Permissions ---"
|
| 31 |
+
if [[ -f "$ENV_FILE" ]]; then
|
| 32 |
+
perms=$(stat -f '%A' "$ENV_FILE" 2>/dev/null || stat -c '%a' "$ENV_FILE" 2>/dev/null)
|
| 33 |
+
if [[ "$perms" == "600" ]]; then
|
| 34 |
+
pass ".env permissions are 600 (owner read/write only)"
|
| 35 |
+
else
|
| 36 |
+
fail ".env permissions are $perms (should be 600). Run: chmod 600 $ENV_FILE"
|
| 37 |
+
fi
|
| 38 |
+
else
|
| 39 |
+
warn ".env file not found at $ENV_FILE"
|
| 40 |
+
fi
|
| 41 |
+
|
| 42 |
+
# 2. Default passwords check
|
| 43 |
+
echo ""
|
| 44 |
+
echo "--- Credentials ---"
|
| 45 |
+
INSECURE_PASSWORDS=("admin" "password" "change-me-on-first-login" "changeme" "testpass123" "testpass1234")
|
| 46 |
+
AUTH_PASS_VAL="${AUTH_PASS:-}"
|
| 47 |
+
if [[ -z "$AUTH_PASS_VAL" ]]; then
|
| 48 |
+
fail "AUTH_PASS is not set"
|
| 49 |
+
else
|
| 50 |
+
insecure=false
|
| 51 |
+
for bad in "${INSECURE_PASSWORDS[@]}"; do
|
| 52 |
+
if [[ "$AUTH_PASS_VAL" == "$bad" ]]; then
|
| 53 |
+
insecure=true; break
|
| 54 |
+
fi
|
| 55 |
+
done
|
| 56 |
+
if $insecure; then
|
| 57 |
+
fail "AUTH_PASS is set to a known insecure default"
|
| 58 |
+
elif [[ ${#AUTH_PASS_VAL} -lt 12 ]]; then
|
| 59 |
+
fail "AUTH_PASS is too short (${#AUTH_PASS_VAL} chars, minimum 12)"
|
| 60 |
+
else
|
| 61 |
+
pass "AUTH_PASS is set to a non-default value (${#AUTH_PASS_VAL} chars)"
|
| 62 |
+
fi
|
| 63 |
+
fi
|
| 64 |
+
|
| 65 |
+
API_KEY_VAL="${API_KEY:-}"
|
| 66 |
+
if [[ -z "$API_KEY_VAL" || "$API_KEY_VAL" == "generate-a-random-key" ]]; then
|
| 67 |
+
fail "API_KEY is not set or uses the default value"
|
| 68 |
+
else
|
| 69 |
+
pass "API_KEY is configured"
|
| 70 |
+
fi
|
| 71 |
+
|
| 72 |
+
# 3. Network config
|
| 73 |
+
echo ""
|
| 74 |
+
echo "--- Network Security ---"
|
| 75 |
+
MC_ALLOWED="${MC_ALLOWED_HOSTS:-}"
|
| 76 |
+
MC_ANY="${MC_ALLOW_ANY_HOST:-}"
|
| 77 |
+
if [[ "$MC_ANY" == "1" || "$MC_ANY" == "true" ]]; then
|
| 78 |
+
fail "MC_ALLOW_ANY_HOST is enabled (any host can connect)"
|
| 79 |
+
elif [[ -n "$MC_ALLOWED" ]]; then
|
| 80 |
+
pass "MC_ALLOWED_HOSTS is configured: $MC_ALLOWED"
|
| 81 |
+
else
|
| 82 |
+
warn "MC_ALLOWED_HOSTS is not set (defaults apply)"
|
| 83 |
+
fi
|
| 84 |
+
|
| 85 |
+
# 4. Cookie/HTTPS config
|
| 86 |
+
echo ""
|
| 87 |
+
echo "--- HTTPS & Cookies ---"
|
| 88 |
+
COOKIE_SECURE="${MC_COOKIE_SECURE:-}"
|
| 89 |
+
if [[ "$COOKIE_SECURE" == "1" || "$COOKIE_SECURE" == "true" ]]; then
|
| 90 |
+
pass "MC_COOKIE_SECURE is enabled"
|
| 91 |
+
else
|
| 92 |
+
warn "MC_COOKIE_SECURE is not enabled (cookies sent over HTTP)"
|
| 93 |
+
fi
|
| 94 |
+
|
| 95 |
+
SAMESITE="${MC_COOKIE_SAMESITE:-strict}"
|
| 96 |
+
if [[ "$SAMESITE" == "strict" ]]; then
|
| 97 |
+
pass "MC_COOKIE_SAMESITE is strict"
|
| 98 |
+
else
|
| 99 |
+
warn "MC_COOKIE_SAMESITE is '$SAMESITE' (strict recommended)"
|
| 100 |
+
fi
|
| 101 |
+
|
| 102 |
+
HSTS="${MC_ENABLE_HSTS:-}"
|
| 103 |
+
if [[ "$HSTS" == "1" ]]; then
|
| 104 |
+
pass "HSTS is enabled"
|
| 105 |
+
else
|
| 106 |
+
warn "HSTS is not enabled (set MC_ENABLE_HSTS=1 for HTTPS deployments)"
|
| 107 |
+
fi
|
| 108 |
+
|
| 109 |
+
# 5. Rate limiting
|
| 110 |
+
echo ""
|
| 111 |
+
echo "--- Rate Limiting ---"
|
| 112 |
+
RL_DISABLED="${MC_DISABLE_RATE_LIMIT:-}"
|
| 113 |
+
if [[ "$RL_DISABLED" == "1" ]]; then
|
| 114 |
+
fail "Rate limiting is disabled (MC_DISABLE_RATE_LIMIT=1)"
|
| 115 |
+
else
|
| 116 |
+
pass "Rate limiting is active"
|
| 117 |
+
fi
|
| 118 |
+
|
| 119 |
+
# 6. Docker security (if running in Docker)
|
| 120 |
+
echo ""
|
| 121 |
+
echo "--- Docker Security ---"
|
| 122 |
+
if command -v docker &>/dev/null; then
|
| 123 |
+
if docker ps --filter name=mission-control --format '{{.Names}}' 2>/dev/null | grep -q mission-control; then
|
| 124 |
+
ro=$(docker inspect mission-control --format '{{.HostConfig.ReadonlyRootfs}}' 2>/dev/null || echo "false")
|
| 125 |
+
if [[ "$ro" == "true" ]]; then
|
| 126 |
+
pass "Container filesystem is read-only"
|
| 127 |
+
else
|
| 128 |
+
warn "Container filesystem is writable (use read_only: true)"
|
| 129 |
+
fi
|
| 130 |
+
|
| 131 |
+
nnp=$(docker inspect mission-control --format '{{.HostConfig.SecurityOpt}}' 2>/dev/null || echo "[]")
|
| 132 |
+
if echo "$nnp" | grep -q "no-new-privileges"; then
|
| 133 |
+
pass "no-new-privileges is set"
|
| 134 |
+
else
|
| 135 |
+
warn "no-new-privileges not set"
|
| 136 |
+
fi
|
| 137 |
+
|
| 138 |
+
user=$(docker inspect mission-control --format '{{.Config.User}}' 2>/dev/null || echo "")
|
| 139 |
+
if [[ -n "$user" && "$user" != "root" && "$user" != "0" ]]; then
|
| 140 |
+
pass "Container runs as non-root user ($user)"
|
| 141 |
+
else
|
| 142 |
+
warn "Container may be running as root"
|
| 143 |
+
fi
|
| 144 |
+
else
|
| 145 |
+
info "Mission Control container not running"
|
| 146 |
+
fi
|
| 147 |
+
else
|
| 148 |
+
info "Docker not installed (skipping container checks)"
|
| 149 |
+
fi
|
| 150 |
+
|
| 151 |
+
# Summary
|
| 152 |
+
echo ""
|
| 153 |
+
echo "=== Security Score: $SCORE / $MAX_SCORE ==="
|
| 154 |
+
if [[ ${#ISSUES[@]} -gt 0 ]]; then
|
| 155 |
+
echo ""
|
| 156 |
+
echo "Issues to fix:"
|
| 157 |
+
for issue in "${ISSUES[@]}"; do
|
| 158 |
+
echo " - $issue"
|
| 159 |
+
done
|
| 160 |
+
fi
|
| 161 |
+
|
| 162 |
+
if [[ $SCORE -eq $MAX_SCORE ]]; then
|
| 163 |
+
echo "All checks passed!"
|
| 164 |
+
elif [[ $SCORE -ge $((MAX_SCORE * 7 / 10)) ]]; then
|
| 165 |
+
echo "Good security posture with minor improvements needed."
|
| 166 |
+
else
|
| 167 |
+
echo "Security improvements recommended before production use."
|
| 168 |
+
fi
|
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
const baseUrl = (process.env.STAGING_BASE_URL || process.env.BASE_URL || '').replace(/\/$/, '')
|
| 3 |
+
const apiKey = process.env.STAGING_API_KEY || process.env.API_KEY || ''
|
| 4 |
+
const authUser = process.env.STAGING_AUTH_USER || process.env.AUTH_USER || ''
|
| 5 |
+
const authPass = process.env.STAGING_AUTH_PASS || process.env.AUTH_PASS || ''
|
| 6 |
+
|
| 7 |
+
if (!baseUrl) {
|
| 8 |
+
console.error('Missing STAGING_BASE_URL (or BASE_URL).')
|
| 9 |
+
process.exit(1)
|
| 10 |
+
}
|
| 11 |
+
if (!apiKey) {
|
| 12 |
+
console.error('Missing STAGING_API_KEY (or API_KEY).')
|
| 13 |
+
process.exit(1)
|
| 14 |
+
}
|
| 15 |
+
if (!authUser || !authPass) {
|
| 16 |
+
console.error('Missing STAGING_AUTH_USER/STAGING_AUTH_PASS (or AUTH_USER/AUTH_PASS).')
|
| 17 |
+
process.exit(1)
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
const headers = {
|
| 21 |
+
'x-api-key': apiKey,
|
| 22 |
+
'content-type': 'application/json',
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
let createdProjectId = null
|
| 26 |
+
let createdTaskId = null
|
| 27 |
+
let createdAgentId = null
|
| 28 |
+
|
| 29 |
+
async function call(path, options = {}) {
|
| 30 |
+
const res = await fetch(`${baseUrl}${path}`, options)
|
| 31 |
+
const text = await res.text()
|
| 32 |
+
let body = null
|
| 33 |
+
try {
|
| 34 |
+
body = text ? JSON.parse(text) : null
|
| 35 |
+
} catch {
|
| 36 |
+
body = { raw: text }
|
| 37 |
+
}
|
| 38 |
+
return { res, body }
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
function assertStatus(actual, expected, label) {
|
| 42 |
+
if (actual !== expected) {
|
| 43 |
+
throw new Error(`${label} failed: expected ${expected}, got ${actual}`)
|
| 44 |
+
}
|
| 45 |
+
console.log(`PASS ${label}`)
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
async function run() {
|
| 49 |
+
const login = await call('/api/auth/login', {
|
| 50 |
+
method: 'POST',
|
| 51 |
+
headers: { 'content-type': 'application/json' },
|
| 52 |
+
body: JSON.stringify({ username: authUser, password: authPass }),
|
| 53 |
+
})
|
| 54 |
+
assertStatus(login.res.status, 200, 'login')
|
| 55 |
+
|
| 56 |
+
const workspaces = await call('/api/workspaces', { headers })
|
| 57 |
+
assertStatus(workspaces.res.status, 200, 'GET /api/workspaces')
|
| 58 |
+
|
| 59 |
+
const suffix = `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`
|
| 60 |
+
const ticketPrefix = `S${String(Date.now()).slice(-5)}`
|
| 61 |
+
|
| 62 |
+
const projectCreate = await call('/api/projects', {
|
| 63 |
+
method: 'POST',
|
| 64 |
+
headers,
|
| 65 |
+
body: JSON.stringify({
|
| 66 |
+
name: `staging-smoke-${suffix}`,
|
| 67 |
+
ticket_prefix: ticketPrefix,
|
| 68 |
+
}),
|
| 69 |
+
})
|
| 70 |
+
assertStatus(projectCreate.res.status, 201, 'POST /api/projects')
|
| 71 |
+
createdProjectId = projectCreate.body?.project?.id
|
| 72 |
+
if (!createdProjectId) throw new Error('project id missing')
|
| 73 |
+
|
| 74 |
+
const projectGet = await call(`/api/projects/${createdProjectId}`, { headers })
|
| 75 |
+
assertStatus(projectGet.res.status, 200, 'GET /api/projects/[id]')
|
| 76 |
+
|
| 77 |
+
const projectPatch = await call(`/api/projects/${createdProjectId}`, {
|
| 78 |
+
method: 'PATCH',
|
| 79 |
+
headers,
|
| 80 |
+
body: JSON.stringify({ description: 'staging smoke update' }),
|
| 81 |
+
})
|
| 82 |
+
assertStatus(projectPatch.res.status, 200, 'PATCH /api/projects/[id]')
|
| 83 |
+
|
| 84 |
+
const agentCreate = await call('/api/agents', {
|
| 85 |
+
method: 'POST',
|
| 86 |
+
headers,
|
| 87 |
+
body: JSON.stringify({ name: `smoke-agent-${suffix}`, role: 'tester' }),
|
| 88 |
+
})
|
| 89 |
+
assertStatus(agentCreate.res.status, 201, 'POST /api/agents')
|
| 90 |
+
createdAgentId = agentCreate.body?.agent?.id
|
| 91 |
+
|
| 92 |
+
const assign = await call(`/api/projects/${createdProjectId}/agents`, {
|
| 93 |
+
method: 'POST',
|
| 94 |
+
headers,
|
| 95 |
+
body: JSON.stringify({ agent_name: `smoke-agent-${suffix}`, role: 'member' }),
|
| 96 |
+
})
|
| 97 |
+
assertStatus(assign.res.status, 201, 'POST /api/projects/[id]/agents')
|
| 98 |
+
|
| 99 |
+
const projectTasksCreate = await call('/api/tasks', {
|
| 100 |
+
method: 'POST',
|
| 101 |
+
headers,
|
| 102 |
+
body: JSON.stringify({
|
| 103 |
+
title: `smoke-task-${suffix}`,
|
| 104 |
+
project_id: createdProjectId,
|
| 105 |
+
priority: 'medium',
|
| 106 |
+
status: 'inbox',
|
| 107 |
+
}),
|
| 108 |
+
})
|
| 109 |
+
assertStatus(projectTasksCreate.res.status, 201, 'POST /api/tasks (project scoped)')
|
| 110 |
+
createdTaskId = projectTasksCreate.body?.task?.id
|
| 111 |
+
|
| 112 |
+
const projectTasksGet = await call(`/api/projects/${createdProjectId}/tasks`, { headers })
|
| 113 |
+
assertStatus(projectTasksGet.res.status, 200, 'GET /api/projects/[id]/tasks')
|
| 114 |
+
|
| 115 |
+
const unassign = await call(`/api/projects/${createdProjectId}/agents?agent_name=${encodeURIComponent(`smoke-agent-${suffix}`)}`, {
|
| 116 |
+
method: 'DELETE',
|
| 117 |
+
headers,
|
| 118 |
+
})
|
| 119 |
+
assertStatus(unassign.res.status, 200, 'DELETE /api/projects/[id]/agents')
|
| 120 |
+
|
| 121 |
+
if (createdTaskId) {
|
| 122 |
+
const deleteTask = await call(`/api/tasks/${createdTaskId}`, {
|
| 123 |
+
method: 'DELETE',
|
| 124 |
+
headers,
|
| 125 |
+
})
|
| 126 |
+
assertStatus(deleteTask.res.status, 200, 'DELETE /api/tasks/[id]')
|
| 127 |
+
createdTaskId = null
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
if (createdProjectId) {
|
| 131 |
+
const deleteProject = await call(`/api/projects/${createdProjectId}?mode=delete`, {
|
| 132 |
+
method: 'DELETE',
|
| 133 |
+
headers,
|
| 134 |
+
})
|
| 135 |
+
assertStatus(deleteProject.res.status, 200, 'DELETE /api/projects/[id]?mode=delete')
|
| 136 |
+
createdProjectId = null
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
if (createdAgentId) {
|
| 140 |
+
const deleteAgent = await call(`/api/agents/${createdAgentId}`, {
|
| 141 |
+
method: 'DELETE',
|
| 142 |
+
headers,
|
| 143 |
+
})
|
| 144 |
+
if (deleteAgent.res.status !== 200 && deleteAgent.res.status !== 404) {
|
| 145 |
+
throw new Error(`DELETE /api/agents/[id] cleanup failed: ${deleteAgent.res.status}`)
|
| 146 |
+
}
|
| 147 |
+
createdAgentId = null
|
| 148 |
+
console.log('PASS cleanup agent')
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
console.log(`\nSmoke test passed for ${baseUrl}`)
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
run().catch(async (error) => {
|
| 155 |
+
console.error(`\nSmoke test failed: ${error.message}`)
|
| 156 |
+
|
| 157 |
+
if (createdTaskId) {
|
| 158 |
+
await call(`/api/tasks/${createdTaskId}`, { method: 'DELETE', headers }).catch(() => {})
|
| 159 |
+
}
|
| 160 |
+
if (createdProjectId) {
|
| 161 |
+
await call(`/api/projects/${createdProjectId}?mode=delete`, { method: 'DELETE', headers }).catch(() => {})
|
| 162 |
+
}
|
| 163 |
+
if (createdAgentId) {
|
| 164 |
+
await call(`/api/agents/${createdAgentId}`, { method: 'DELETE', headers }).catch(() => {})
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
process.exit(1)
|
| 168 |
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -euo pipefail
|
| 4 |
+
|
| 5 |
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 6 |
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
| 7 |
+
STANDALONE_DIR="$PROJECT_ROOT/.next/standalone"
|
| 8 |
+
STANDALONE_NEXT_DIR="$STANDALONE_DIR/.next"
|
| 9 |
+
STANDALONE_STATIC_DIR="$STANDALONE_NEXT_DIR/static"
|
| 10 |
+
SOURCE_STATIC_DIR="$PROJECT_ROOT/.next/static"
|
| 11 |
+
SOURCE_PUBLIC_DIR="$PROJECT_ROOT/public"
|
| 12 |
+
STANDALONE_PUBLIC_DIR="$STANDALONE_DIR/public"
|
| 13 |
+
|
| 14 |
+
if [[ ! -f "$STANDALONE_DIR/server.js" ]]; then
|
| 15 |
+
echo "error: standalone server missing at $STANDALONE_DIR/server.js" >&2
|
| 16 |
+
echo "run 'pnpm build' first" >&2
|
| 17 |
+
exit 1
|
| 18 |
+
fi
|
| 19 |
+
|
| 20 |
+
mkdir -p "$STANDALONE_NEXT_DIR"
|
| 21 |
+
|
| 22 |
+
if [[ -d "$SOURCE_STATIC_DIR" ]]; then
|
| 23 |
+
rm -rf "$STANDALONE_STATIC_DIR"
|
| 24 |
+
cp -R "$SOURCE_STATIC_DIR" "$STANDALONE_STATIC_DIR"
|
| 25 |
+
fi
|
| 26 |
+
|
| 27 |
+
if [[ -d "$SOURCE_PUBLIC_DIR" ]]; then
|
| 28 |
+
rm -rf "$STANDALONE_PUBLIC_DIR"
|
| 29 |
+
cp -R "$SOURCE_PUBLIC_DIR" "$STANDALONE_PUBLIC_DIR"
|
| 30 |
+
fi
|
| 31 |
+
|
| 32 |
+
cd "$STANDALONE_DIR"
|
| 33 |
+
exec node server.js
|
|
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Mission Control Station Doctor
|
| 3 |
+
# Local diagnostics — no auth required, runs on the host.
|
| 4 |
+
#
|
| 5 |
+
# Usage: bash scripts/station-doctor.sh [--port PORT]
|
| 6 |
+
|
| 7 |
+
set -euo pipefail
|
| 8 |
+
|
| 9 |
+
MC_PORT="${1:-3000}"
|
| 10 |
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 11 |
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
| 12 |
+
|
| 13 |
+
# Parse args
|
| 14 |
+
for arg in "$@"; do
|
| 15 |
+
case "$arg" in
|
| 16 |
+
--port) shift; MC_PORT="$1"; shift ;;
|
| 17 |
+
esac
|
| 18 |
+
done
|
| 19 |
+
|
| 20 |
+
PASS=0
|
| 21 |
+
WARN=0
|
| 22 |
+
FAIL=0
|
| 23 |
+
|
| 24 |
+
pass() { echo " [PASS] $1"; ((PASS++)); }
|
| 25 |
+
warn() { echo " [WARN] $1"; ((WARN++)); }
|
| 26 |
+
fail() { echo " [FAIL] $1"; ((FAIL++)); }
|
| 27 |
+
info() { echo " [INFO] $1"; }
|
| 28 |
+
|
| 29 |
+
echo "=== Mission Control Station Doctor ==="
|
| 30 |
+
echo ""
|
| 31 |
+
|
| 32 |
+
# ── 1. Process / Container check ─────────────────────────────────────────────
|
| 33 |
+
echo "--- Service Status ---"
|
| 34 |
+
|
| 35 |
+
RUNNING_IN_DOCKER=false
|
| 36 |
+
if command -v docker &>/dev/null; then
|
| 37 |
+
if docker ps --filter name=mission-control --format '{{.Names}}' 2>/dev/null | grep -q mission-control; then
|
| 38 |
+
RUNNING_IN_DOCKER=true
|
| 39 |
+
health=$(docker inspect mission-control --format '{{.State.Health.Status}}' 2>/dev/null || echo "none")
|
| 40 |
+
if [[ "$health" == "healthy" ]]; then
|
| 41 |
+
pass "Docker container is healthy"
|
| 42 |
+
elif [[ "$health" == "starting" ]]; then
|
| 43 |
+
warn "Docker container is starting"
|
| 44 |
+
else
|
| 45 |
+
fail "Docker container health: $health"
|
| 46 |
+
fi
|
| 47 |
+
fi
|
| 48 |
+
fi
|
| 49 |
+
|
| 50 |
+
if ! $RUNNING_IN_DOCKER; then
|
| 51 |
+
if pgrep -f "node.*server.js" &>/dev/null || pgrep -f "next-server" &>/dev/null; then
|
| 52 |
+
pass "Mission Control process is running"
|
| 53 |
+
else
|
| 54 |
+
fail "Mission Control process not found"
|
| 55 |
+
fi
|
| 56 |
+
fi
|
| 57 |
+
|
| 58 |
+
# ── 2. Port check ────────────────────────────────────────────────────────────
|
| 59 |
+
echo ""
|
| 60 |
+
echo "--- Network ---"
|
| 61 |
+
|
| 62 |
+
if curl -sf "http://localhost:$MC_PORT/login" &>/dev/null; then
|
| 63 |
+
pass "Port $MC_PORT is responding"
|
| 64 |
+
else
|
| 65 |
+
fail "Port $MC_PORT is not responding"
|
| 66 |
+
fi
|
| 67 |
+
|
| 68 |
+
# ── 3. API health ─────────────────────────────────────────────────────────────
|
| 69 |
+
# Try unauthenticated — will get 401 but proves the server is up
|
| 70 |
+
http_code=$(curl -sf -o /dev/null -w "%{http_code}" "http://localhost:$MC_PORT/api/status?action=health" 2>/dev/null || echo "000")
|
| 71 |
+
if [[ "$http_code" == "200" ]]; then
|
| 72 |
+
pass "Health API responding (200)"
|
| 73 |
+
elif [[ "$http_code" == "401" ]]; then
|
| 74 |
+
pass "Health API responding (auth required — expected)"
|
| 75 |
+
elif [[ "$http_code" == "000" ]]; then
|
| 76 |
+
fail "Health API not reachable"
|
| 77 |
+
else
|
| 78 |
+
warn "Health API returned HTTP $http_code"
|
| 79 |
+
fi
|
| 80 |
+
|
| 81 |
+
# ── 4. Disk space ─────────────────────────────────────────────────────────────
|
| 82 |
+
echo ""
|
| 83 |
+
echo "--- Disk ---"
|
| 84 |
+
|
| 85 |
+
usage_pct=$(df -h "$PROJECT_ROOT" 2>/dev/null | tail -1 | awk '{for(i=1;i<=NF;i++) if($i ~ /%/) print $i}' | tr -d '%')
|
| 86 |
+
if [[ -n "$usage_pct" ]]; then
|
| 87 |
+
if [[ "$usage_pct" -lt 85 ]]; then
|
| 88 |
+
pass "Disk usage: ${usage_pct}%"
|
| 89 |
+
elif [[ "$usage_pct" -lt 95 ]]; then
|
| 90 |
+
warn "Disk usage: ${usage_pct}% (getting full)"
|
| 91 |
+
else
|
| 92 |
+
fail "Disk usage: ${usage_pct}% (critical)"
|
| 93 |
+
fi
|
| 94 |
+
fi
|
| 95 |
+
|
| 96 |
+
# ── 5. Database integrity ────────────────────────────────────────────────────
|
| 97 |
+
echo ""
|
| 98 |
+
echo "--- Database ---"
|
| 99 |
+
|
| 100 |
+
DB_PATH="$PROJECT_ROOT/.data/mission-control.db"
|
| 101 |
+
if [[ -f "$DB_PATH" ]]; then
|
| 102 |
+
db_size=$(du -h "$DB_PATH" 2>/dev/null | cut -f1)
|
| 103 |
+
pass "Database exists ($db_size)"
|
| 104 |
+
|
| 105 |
+
# SQLite integrity check
|
| 106 |
+
if command -v sqlite3 &>/dev/null; then
|
| 107 |
+
integrity=$(sqlite3 "$DB_PATH" "PRAGMA integrity_check;" 2>/dev/null || echo "error")
|
| 108 |
+
if [[ "$integrity" == "ok" ]]; then
|
| 109 |
+
pass "Database integrity check passed"
|
| 110 |
+
else
|
| 111 |
+
fail "Database integrity check failed: $integrity"
|
| 112 |
+
fi
|
| 113 |
+
|
| 114 |
+
# WAL mode check
|
| 115 |
+
journal=$(sqlite3 "$DB_PATH" "PRAGMA journal_mode;" 2>/dev/null || echo "unknown")
|
| 116 |
+
if [[ "$journal" == "wal" ]]; then
|
| 117 |
+
pass "WAL mode enabled"
|
| 118 |
+
else
|
| 119 |
+
warn "Journal mode: $journal (WAL recommended)"
|
| 120 |
+
fi
|
| 121 |
+
else
|
| 122 |
+
info "sqlite3 not found — skipping integrity check"
|
| 123 |
+
fi
|
| 124 |
+
else
|
| 125 |
+
if $RUNNING_IN_DOCKER; then
|
| 126 |
+
info "Database is inside Docker volume (cannot check directly)"
|
| 127 |
+
else
|
| 128 |
+
warn "Database not found at $DB_PATH"
|
| 129 |
+
fi
|
| 130 |
+
fi
|
| 131 |
+
|
| 132 |
+
# ── 6. Backup age ────────────────────────────────────────────────────────────
|
| 133 |
+
echo ""
|
| 134 |
+
echo "--- Backups ---"
|
| 135 |
+
|
| 136 |
+
BACKUP_DIR="$PROJECT_ROOT/.data/backups"
|
| 137 |
+
if [[ -d "$BACKUP_DIR" ]]; then
|
| 138 |
+
latest_backup=$(find "$BACKUP_DIR" -name "*.db" -type f 2>/dev/null | sort -r | head -1)
|
| 139 |
+
if [[ -n "$latest_backup" ]]; then
|
| 140 |
+
if [[ "$(uname)" == "Darwin" ]]; then
|
| 141 |
+
backup_age_days=$(( ($(date +%s) - $(stat -f %m "$latest_backup")) / 86400 ))
|
| 142 |
+
else
|
| 143 |
+
backup_age_days=$(( ($(date +%s) - $(stat -c %Y "$latest_backup")) / 86400 ))
|
| 144 |
+
fi
|
| 145 |
+
backup_name=$(basename "$latest_backup")
|
| 146 |
+
if [[ "$backup_age_days" -lt 1 ]]; then
|
| 147 |
+
pass "Latest backup: $backup_name (today)"
|
| 148 |
+
elif [[ "$backup_age_days" -lt 7 ]]; then
|
| 149 |
+
pass "Latest backup: $backup_name (${backup_age_days}d ago)"
|
| 150 |
+
elif [[ "$backup_age_days" -lt 30 ]]; then
|
| 151 |
+
warn "Latest backup: $backup_name (${backup_age_days}d ago — consider more frequent backups)"
|
| 152 |
+
else
|
| 153 |
+
fail "Latest backup: $backup_name (${backup_age_days}d ago — stale!)"
|
| 154 |
+
fi
|
| 155 |
+
else
|
| 156 |
+
warn "No backups found in $BACKUP_DIR"
|
| 157 |
+
fi
|
| 158 |
+
else
|
| 159 |
+
warn "No backup directory at $BACKUP_DIR"
|
| 160 |
+
fi
|
| 161 |
+
|
| 162 |
+
# ── 7. OpenClaw gateway ─────────────────────────────────────────────────────
|
| 163 |
+
echo ""
|
| 164 |
+
echo "--- OpenClaw Gateway ---"
|
| 165 |
+
|
| 166 |
+
GW_HOST="${OPENCLAW_GATEWAY_HOST:-127.0.0.1}"
|
| 167 |
+
GW_PORT="${OPENCLAW_GATEWAY_PORT:-18789}"
|
| 168 |
+
|
| 169 |
+
if nc -z "$GW_HOST" "$GW_PORT" 2>/dev/null || (echo > "/dev/tcp/$GW_HOST/$GW_PORT") 2>/dev/null; then
|
| 170 |
+
pass "Gateway reachable at $GW_HOST:$GW_PORT"
|
| 171 |
+
else
|
| 172 |
+
info "Gateway not reachable at $GW_HOST:$GW_PORT"
|
| 173 |
+
fi
|
| 174 |
+
|
| 175 |
+
# ── Summary ──────────────────────────────────────────────────────────────────
|
| 176 |
+
echo ""
|
| 177 |
+
TOTAL=$((PASS + WARN + FAIL))
|
| 178 |
+
echo "=== Results: $PASS passed, $WARN warnings, $FAIL failures (of $TOTAL checks) ==="
|
| 179 |
+
|
| 180 |
+
if [[ $FAIL -gt 0 ]]; then
|
| 181 |
+
echo "Status: UNHEALTHY"
|
| 182 |
+
exit 1
|
| 183 |
+
elif [[ $WARN -gt 0 ]]; then
|
| 184 |
+
echo "Status: DEGRADED"
|
| 185 |
+
exit 0
|
| 186 |
+
else
|
| 187 |
+
echo "Status: HEALTHY"
|
| 188 |
+
exit 0
|
| 189 |
+
fi
|
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Mission Control Installer Skill
|
| 2 |
+
|
| 3 |
+
Install and configure Mission Control on any Linux or macOS system.
|
| 4 |
+
|
| 5 |
+
## What This Skill Does
|
| 6 |
+
|
| 7 |
+
1. Detects the target OS and available runtimes (Docker or Node.js 20+)
|
| 8 |
+
2. Clones or updates the Mission Control repository
|
| 9 |
+
3. Generates a secure `.env` with random credentials
|
| 10 |
+
4. Starts the dashboard via Docker Compose or local Node.js
|
| 11 |
+
5. Runs an OpenClaw fleet health check (cleans stale PIDs, old logs, validates gateway)
|
| 12 |
+
6. Prints the access URL and admin credentials
|
| 13 |
+
|
| 14 |
+
## Usage
|
| 15 |
+
|
| 16 |
+
Run the installer script:
|
| 17 |
+
|
| 18 |
+
```bash
|
| 19 |
+
# Auto-detect deployment mode (prefers Docker)
|
| 20 |
+
bash install.sh
|
| 21 |
+
|
| 22 |
+
# Force Docker deployment
|
| 23 |
+
bash install.sh --docker
|
| 24 |
+
|
| 25 |
+
# Force local deployment (Node.js + pnpm)
|
| 26 |
+
bash install.sh --local
|
| 27 |
+
|
| 28 |
+
# Custom port
|
| 29 |
+
bash install.sh --port 8080
|
| 30 |
+
|
| 31 |
+
# Skip OpenClaw fleet check
|
| 32 |
+
bash install.sh --skip-openclaw
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
Or as a one-liner:
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
curl -fsSL https://raw.githubusercontent.com/builderz-labs/mission-control/main/install.sh | bash
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
## Prerequisites
|
| 42 |
+
|
| 43 |
+
- **Docker mode**: Docker Engine with Docker Compose v2
|
| 44 |
+
- **Local mode**: Node.js 20+, pnpm (auto-installed via corepack if missing)
|
| 45 |
+
- **Both**: git (to clone the repository)
|
| 46 |
+
|
| 47 |
+
## Post-Install
|
| 48 |
+
|
| 49 |
+
After installation:
|
| 50 |
+
|
| 51 |
+
1. Open `http://localhost:3000` (or your configured port)
|
| 52 |
+
2. Log in with the credentials printed by the installer (also in `.env`)
|
| 53 |
+
3. Configure your OpenClaw gateway connection in Settings
|
| 54 |
+
4. Register agents via the Agents panel
|
| 55 |
+
|
| 56 |
+
## Environment Configuration
|
| 57 |
+
|
| 58 |
+
The installer generates a `.env` from `.env.example` with secure random values for:
|
| 59 |
+
|
| 60 |
+
- `AUTH_PASS` — 24-character random password
|
| 61 |
+
- `API_KEY` — 32-character hex API key
|
| 62 |
+
- `AUTH_SECRET` — 32-character session secret
|
| 63 |
+
|
| 64 |
+
To regenerate credentials independently:
|
| 65 |
+
|
| 66 |
+
```bash
|
| 67 |
+
bash scripts/generate-env.sh --force
|
| 68 |
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "mission-control-installer",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"description": "Install and configure Mission Control — the OpenClaw agent orchestration dashboard",
|
| 5 |
+
"author": "Builderz Labs",
|
| 6 |
+
"license": "MIT",
|
| 7 |
+
"tools": ["exec", "fs"],
|
| 8 |
+
"parameters": {
|
| 9 |
+
"deployment_mode": {
|
| 10 |
+
"type": "string",
|
| 11 |
+
"enum": ["docker", "local"],
|
| 12 |
+
"default": "docker",
|
| 13 |
+
"description": "How to deploy Mission Control"
|
| 14 |
+
},
|
| 15 |
+
"port": {
|
| 16 |
+
"type": "number",
|
| 17 |
+
"default": 3000,
|
| 18 |
+
"description": "Port for the Mission Control dashboard"
|
| 19 |
+
},
|
| 20 |
+
"install_dir": {
|
| 21 |
+
"type": "string",
|
| 22 |
+
"default": "",
|
| 23 |
+
"description": "Installation directory (defaults to ./mission-control)"
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
"tags": ["mission-control", "dashboard", "installer", "docker"]
|
| 27 |
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Mission Control Management Skill
|
| 2 |
+
|
| 3 |
+
Manage a running Mission Control instance programmatically.
|
| 4 |
+
|
| 5 |
+
## API Endpoints
|
| 6 |
+
|
| 7 |
+
All endpoints require authentication via `x-api-key` header or session cookie.
|
| 8 |
+
|
| 9 |
+
### Health Check
|
| 10 |
+
|
| 11 |
+
```bash
|
| 12 |
+
# Quick health status
|
| 13 |
+
curl -H "x-api-key: $API_KEY" http://localhost:3000/api/status?action=health
|
| 14 |
+
|
| 15 |
+
# Response: { "status": "healthy", "version": "1.3.0", "checks": [...] }
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
Possible statuses: `healthy`, `degraded`, `unhealthy`
|
| 19 |
+
|
| 20 |
+
### System Overview
|
| 21 |
+
|
| 22 |
+
```bash
|
| 23 |
+
# Full system status (memory, disk, sessions, processes)
|
| 24 |
+
curl -H "x-api-key: $API_KEY" http://localhost:3000/api/status?action=overview
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
### Diagnostics (Admin Only)
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
# Comprehensive diagnostics including security posture
|
| 31 |
+
curl -H "x-api-key: $API_KEY" http://localhost:3000/api/diagnostics
|
| 32 |
+
|
| 33 |
+
# Response includes:
|
| 34 |
+
# - system: node version, platform, memory, docker detection
|
| 35 |
+
# - security: score (0-100) with individual checks
|
| 36 |
+
# - database: size, WAL mode, migration version
|
| 37 |
+
# - gateway: configured, reachable, host/port
|
| 38 |
+
# - agents: total count, by status
|
| 39 |
+
# - retention: configured retention policies
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
### Check for Updates
|
| 43 |
+
|
| 44 |
+
```bash
|
| 45 |
+
curl -H "x-api-key: $API_KEY" http://localhost:3000/api/releases/check
|
| 46 |
+
|
| 47 |
+
# Response: { "updateAvailable": true, "currentVersion": "1.3.0", "latestVersion": "1.4.0", ... }
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
### Trigger Update
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
# Apply available update (bare-metal only; Docker returns instructions)
|
| 54 |
+
curl -X POST -H "x-api-key: $API_KEY" http://localhost:3000/api/releases/update
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
### Database Backup
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
curl -X POST -H "x-api-key: $API_KEY" http://localhost:3000/api/backup
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
### Agent Management
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
# List agents
|
| 67 |
+
curl -H "x-api-key: $API_KEY" http://localhost:3000/api/agents
|
| 68 |
+
|
| 69 |
+
# Register an agent
|
| 70 |
+
curl -X POST -H "x-api-key: $API_KEY" \
|
| 71 |
+
-H "Content-Type: application/json" \
|
| 72 |
+
-d '{"name": "my-agent", "type": "openclaw"}' \
|
| 73 |
+
http://localhost:3000/api/agents
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
## Station Doctor
|
| 77 |
+
|
| 78 |
+
For local diagnostics without API access:
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
bash scripts/station-doctor.sh
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
Checks: Docker health, port availability, disk space, DB integrity, backup age.
|
| 85 |
+
|
| 86 |
+
## Common Workflows
|
| 87 |
+
|
| 88 |
+
### Automated Health Monitoring
|
| 89 |
+
|
| 90 |
+
```bash
|
| 91 |
+
# Check health and alert if unhealthy
|
| 92 |
+
STATUS=$(curl -sf -H "x-api-key: $API_KEY" http://localhost:3000/api/status?action=health | jq -r '.status')
|
| 93 |
+
if [ "$STATUS" != "healthy" ]; then
|
| 94 |
+
echo "ALERT: Mission Control is $STATUS"
|
| 95 |
+
fi
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
### Pre-Upgrade Checklist
|
| 99 |
+
|
| 100 |
+
1. Check for updates: `GET /api/releases/check`
|
| 101 |
+
2. Create backup: `POST /api/backup`
|
| 102 |
+
3. Run diagnostics: `GET /api/diagnostics` (verify no active tasks)
|
| 103 |
+
4. Apply update: `POST /api/releases/update` (or `docker pull` + recreate for Docker)
|
| 104 |
+
5. Verify health: `GET /api/status?action=health`
|
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "mission-control-manage",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"description": "Manage a running Mission Control instance — health checks, diagnostics, upgrades, backups",
|
| 5 |
+
"author": "Builderz Labs",
|
| 6 |
+
"license": "MIT",
|
| 7 |
+
"tools": ["exec", "http"],
|
| 8 |
+
"parameters": {
|
| 9 |
+
"base_url": {
|
| 10 |
+
"type": "string",
|
| 11 |
+
"default": "http://localhost:3000",
|
| 12 |
+
"description": "Mission Control base URL"
|
| 13 |
+
},
|
| 14 |
+
"api_key": {
|
| 15 |
+
"type": "string",
|
| 16 |
+
"description": "API key for authentication (x-api-key header)"
|
| 17 |
+
}
|
| 18 |
+
},
|
| 19 |
+
"tags": ["mission-control", "management", "health", "upgrade", "backup"]
|
| 20 |
+
}
|
|
@@ -1,18 +1,15 @@
|
|
| 1 |
'use client'
|
| 2 |
|
| 3 |
-
import { useEffect, useState } from 'react'
|
| 4 |
import { usePathname, useRouter } from 'next/navigation'
|
| 5 |
import { NavRail } from '@/components/layout/nav-rail'
|
| 6 |
import { HeaderBar } from '@/components/layout/header-bar'
|
| 7 |
import { LiveFeed } from '@/components/layout/live-feed'
|
| 8 |
import { Dashboard } from '@/components/dashboard/dashboard'
|
| 9 |
-
import { AgentSpawnPanel } from '@/components/panels/agent-spawn-panel'
|
| 10 |
import { LogViewerPanel } from '@/components/panels/log-viewer-panel'
|
| 11 |
import { CronManagementPanel } from '@/components/panels/cron-management-panel'
|
| 12 |
import { MemoryBrowserPanel } from '@/components/panels/memory-browser-panel'
|
| 13 |
-
import {
|
| 14 |
-
import { AgentCostPanel } from '@/components/panels/agent-cost-panel'
|
| 15 |
-
import { SessionDetailsPanel } from '@/components/panels/session-details-panel'
|
| 16 |
import { TaskBoardPanel } from '@/components/panels/task-board-panel'
|
| 17 |
import { ActivityFeedPanel } from '@/components/panels/activity-feed-panel'
|
| 18 |
import { AgentSquadPanelPhase3 } from '@/components/panels/agent-squad-panel-phase3'
|
|
@@ -22,7 +19,6 @@ import { OrchestrationBar } from '@/components/panels/orchestration-bar'
|
|
| 22 |
import { NotificationsPanel } from '@/components/panels/notifications-panel'
|
| 23 |
import { UserManagementPanel } from '@/components/panels/user-management-panel'
|
| 24 |
import { AuditTrailPanel } from '@/components/panels/audit-trail-panel'
|
| 25 |
-
import { AgentHistoryPanel } from '@/components/panels/agent-history-panel'
|
| 26 |
import { WebhookPanel } from '@/components/panels/webhook-panel'
|
| 27 |
import { SettingsPanel } from '@/components/panels/settings-panel'
|
| 28 |
import { GatewayConfigPanel } from '@/components/panels/gateway-config-panel'
|
|
@@ -32,36 +28,176 @@ import { MultiGatewayPanel } from '@/components/panels/multi-gateway-panel'
|
|
| 32 |
import { SuperAdminPanel } from '@/components/panels/super-admin-panel'
|
| 33 |
import { OfficePanel } from '@/components/panels/office-panel'
|
| 34 |
import { GitHubSyncPanel } from '@/components/panels/github-sync-panel'
|
| 35 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
import { ChatPanel } from '@/components/chat/chat-panel'
|
|
|
|
| 37 |
import { ErrorBoundary } from '@/components/ErrorBoundary'
|
| 38 |
import { LocalModeBanner } from '@/components/layout/local-mode-banner'
|
| 39 |
import { UpdateBanner } from '@/components/layout/update-banner'
|
| 40 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
import { useWebSocket } from '@/lib/websocket'
|
| 42 |
import { useServerEvents } from '@/lib/use-server-events'
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
import { useMissionControl } from '@/store'
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
export default function Home() {
|
| 46 |
const router = useRouter()
|
| 47 |
const { connect } = useWebSocket()
|
| 48 |
-
const { activeTab, setActiveTab, setCurrentUser, setDashboardMode, setGatewayAvailable, setSubscription, setUpdateAvailable, liveFeedOpen, toggleLiveFeed } = useMissionControl()
|
| 49 |
|
| 50 |
// Sync URL → Zustand activeTab
|
| 51 |
const pathname = usePathname()
|
| 52 |
const panelFromUrl = pathname === '/' ? 'overview' : pathname.slice(1)
|
|
|
|
| 53 |
|
| 54 |
useEffect(() => {
|
| 55 |
-
|
| 56 |
-
}, [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
// Connect to SSE for real-time local DB events (tasks, agents, chat, etc.)
|
| 59 |
useServerEvents()
|
| 60 |
const [isClient, setIsClient] = useState(false)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
useEffect(() => {
|
| 63 |
setIsClient(true)
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
// Fetch current user
|
| 66 |
fetch('/api/auth/me')
|
| 67 |
.then(async (res) => {
|
|
@@ -71,8 +207,8 @@ export default function Home() {
|
|
| 71 |
}
|
| 72 |
return null
|
| 73 |
})
|
| 74 |
-
.then(data => { if (data?.user) setCurrentUser(data.user) })
|
| 75 |
-
.catch(() => {})
|
| 76 |
|
| 77 |
// Check for available updates
|
| 78 |
fetch('/api/releases/check')
|
|
@@ -88,16 +224,41 @@ export default function Home() {
|
|
| 88 |
})
|
| 89 |
.catch(() => {})
|
| 90 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
// Check capabilities, then conditionally connect to gateway
|
| 92 |
fetch('/api/status?action=capabilities')
|
| 93 |
.then(res => res.ok ? res.json() : null)
|
| 94 |
-
.then(data => {
|
| 95 |
if (data?.subscription) {
|
| 96 |
setSubscription(data.subscription)
|
| 97 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
if (data && data.gateway === false) {
|
| 99 |
setDashboardMode('local')
|
| 100 |
setGatewayAvailable(false)
|
|
|
|
|
|
|
|
|
|
| 101 |
// Skip WebSocket connect — no gateway to talk to
|
| 102 |
return
|
| 103 |
}
|
|
@@ -105,45 +266,86 @@ export default function Home() {
|
|
| 105 |
setDashboardMode('full')
|
| 106 |
setGatewayAvailable(true)
|
| 107 |
}
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
const
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
const wsUrl = explicitWsUrl || `${gatewayProto}://${gatewayHost}:${gatewayPort}`
|
| 117 |
-
connect(wsUrl, wsToken)
|
| 118 |
})
|
| 119 |
.catch(() => {
|
| 120 |
// If capabilities check fails, still try to connect
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
const gatewayProto =
|
| 126 |
-
process.env.NEXT_PUBLIC_GATEWAY_PROTOCOL ||
|
| 127 |
-
(window.location.protocol === 'https:' ? 'wss' : 'ws')
|
| 128 |
-
const wsUrl = explicitWsUrl || `${gatewayProto}://${gatewayHost}:${gatewayPort}`
|
| 129 |
-
connect(wsUrl, wsToken)
|
| 130 |
})
|
| 131 |
-
}, [connect, pathname, router, setCurrentUser, setDashboardMode, setGatewayAvailable, setSubscription, setUpdateAvailable])
|
| 132 |
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
}
|
| 148 |
|
| 149 |
return (
|
|
@@ -151,33 +353,49 @@ export default function Home() {
|
|
| 151 |
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:top-2 focus:left-2 focus:px-4 focus:py-2 focus:bg-primary focus:text-primary-foreground focus:rounded-md focus:text-sm focus:font-medium">
|
| 152 |
Skip to main content
|
| 153 |
</a>
|
|
|
|
| 154 |
{/* Left: Icon rail navigation (hidden on mobile, shown as bottom bar instead) */}
|
| 155 |
-
<NavRail />
|
| 156 |
|
| 157 |
{/* Center: Header + Content */}
|
| 158 |
<div className="flex-1 flex flex-col min-w-0">
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
<ErrorBoundary key={activeTab}>
|
| 166 |
<ContentRouter tab={activeTab} />
|
| 167 |
</ErrorBoundary>
|
| 168 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
</main>
|
| 170 |
</div>
|
| 171 |
|
| 172 |
{/* Right: Live feed (hidden on mobile) */}
|
| 173 |
-
{liveFeedOpen && (
|
| 174 |
<div className="hidden lg:flex h-full">
|
| 175 |
<LiveFeed />
|
| 176 |
</div>
|
| 177 |
)}
|
| 178 |
|
| 179 |
{/* Floating button to reopen LiveFeed when closed */}
|
| 180 |
-
{!liveFeedOpen && (
|
| 181 |
<button
|
| 182 |
onClick={toggleLiveFeed}
|
| 183 |
className="hidden lg:flex fixed right-0 top-1/2 -translate-y-1/2 z-30 w-6 h-12 items-center justify-center bg-card border border-r-0 border-border rounded-l-md text-muted-foreground hover:text-foreground hover:bg-secondary transition-all duration-200"
|
|
@@ -190,22 +408,70 @@ export default function Home() {
|
|
| 190 |
)}
|
| 191 |
|
| 192 |
{/* Chat panel overlay */}
|
| 193 |
-
<ChatPanel />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
</div>
|
| 195 |
)
|
| 196 |
}
|
| 197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
function ContentRouter({ tab }: { tab: string }) {
|
| 199 |
-
const { dashboardMode } = useMissionControl()
|
|
|
|
| 200 |
const isLocal = dashboardMode === 'local'
|
| 201 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
switch (tab) {
|
| 203 |
case 'overview':
|
| 204 |
return (
|
| 205 |
<>
|
| 206 |
<Dashboard />
|
| 207 |
{!isLocal && (
|
| 208 |
-
<div className="mt-4 mx-4 mb-4 rounded-
|
| 209 |
<AgentCommsPanel />
|
| 210 |
</div>
|
| 211 |
)}
|
|
@@ -217,38 +483,31 @@ function ContentRouter({ tab }: { tab: string }) {
|
|
| 217 |
return (
|
| 218 |
<>
|
| 219 |
<OrchestrationBar />
|
|
|
|
| 220 |
<AgentSquadPanelPhase3 />
|
| 221 |
-
{!isLocal && (
|
| 222 |
-
<div className="mt-4 mx-4 mb-4 rounded-xl border border-border bg-card overflow-hidden">
|
| 223 |
-
<AgentCommsPanel />
|
| 224 |
-
</div>
|
| 225 |
-
)}
|
| 226 |
</>
|
| 227 |
)
|
| 228 |
-
case 'activity':
|
| 229 |
-
return <ActivityFeedPanel />
|
| 230 |
case 'notifications':
|
| 231 |
return <NotificationsPanel />
|
| 232 |
case 'standup':
|
| 233 |
return <StandupPanel />
|
| 234 |
-
case 'spawn':
|
| 235 |
-
return <AgentSpawnPanel />
|
| 236 |
case 'sessions':
|
| 237 |
-
return <
|
| 238 |
case 'logs':
|
| 239 |
return <LogViewerPanel />
|
| 240 |
case 'cron':
|
| 241 |
return <CronManagementPanel />
|
| 242 |
case 'memory':
|
| 243 |
return <MemoryBrowserPanel />
|
|
|
|
| 244 |
case 'tokens':
|
| 245 |
-
return <TokenDashboardPanel />
|
| 246 |
case 'agent-costs':
|
| 247 |
-
return <
|
| 248 |
case 'users':
|
| 249 |
return <UserManagementPanel />
|
| 250 |
case 'history':
|
| 251 |
-
|
|
|
|
| 252 |
case 'audit':
|
| 253 |
return <AuditTrailPanel />
|
| 254 |
case 'webhooks':
|
|
@@ -256,24 +515,53 @@ function ContentRouter({ tab }: { tab: string }) {
|
|
| 256 |
case 'alerts':
|
| 257 |
return <AlertRulesPanel />
|
| 258 |
case 'gateways':
|
|
|
|
| 259 |
return <MultiGatewayPanel />
|
| 260 |
case 'gateway-config':
|
|
|
|
| 261 |
return <GatewayConfigPanel />
|
| 262 |
case 'integrations':
|
| 263 |
return <IntegrationsPanel />
|
| 264 |
case 'settings':
|
| 265 |
return <SettingsPanel />
|
|
|
|
|
|
|
| 266 |
case 'github':
|
| 267 |
return <GitHubSyncPanel />
|
| 268 |
case 'office':
|
| 269 |
return <OfficePanel />
|
| 270 |
-
case '
|
| 271 |
-
return <
|
| 272 |
-
case '
|
| 273 |
-
return <
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
return <
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
}
|
| 279 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
'use client'
|
| 2 |
|
| 3 |
+
import { createElement, useEffect, useState } from 'react'
|
| 4 |
import { usePathname, useRouter } from 'next/navigation'
|
| 5 |
import { NavRail } from '@/components/layout/nav-rail'
|
| 6 |
import { HeaderBar } from '@/components/layout/header-bar'
|
| 7 |
import { LiveFeed } from '@/components/layout/live-feed'
|
| 8 |
import { Dashboard } from '@/components/dashboard/dashboard'
|
|
|
|
| 9 |
import { LogViewerPanel } from '@/components/panels/log-viewer-panel'
|
| 10 |
import { CronManagementPanel } from '@/components/panels/cron-management-panel'
|
| 11 |
import { MemoryBrowserPanel } from '@/components/panels/memory-browser-panel'
|
| 12 |
+
import { CostTrackerPanel } from '@/components/panels/cost-tracker-panel'
|
|
|
|
|
|
|
| 13 |
import { TaskBoardPanel } from '@/components/panels/task-board-panel'
|
| 14 |
import { ActivityFeedPanel } from '@/components/panels/activity-feed-panel'
|
| 15 |
import { AgentSquadPanelPhase3 } from '@/components/panels/agent-squad-panel-phase3'
|
|
|
|
| 19 |
import { NotificationsPanel } from '@/components/panels/notifications-panel'
|
| 20 |
import { UserManagementPanel } from '@/components/panels/user-management-panel'
|
| 21 |
import { AuditTrailPanel } from '@/components/panels/audit-trail-panel'
|
|
|
|
| 22 |
import { WebhookPanel } from '@/components/panels/webhook-panel'
|
| 23 |
import { SettingsPanel } from '@/components/panels/settings-panel'
|
| 24 |
import { GatewayConfigPanel } from '@/components/panels/gateway-config-panel'
|
|
|
|
| 28 |
import { SuperAdminPanel } from '@/components/panels/super-admin-panel'
|
| 29 |
import { OfficePanel } from '@/components/panels/office-panel'
|
| 30 |
import { GitHubSyncPanel } from '@/components/panels/github-sync-panel'
|
| 31 |
+
import { SkillsPanel } from '@/components/panels/skills-panel'
|
| 32 |
+
import { LocalAgentsDocPanel } from '@/components/panels/local-agents-doc-panel'
|
| 33 |
+
import { ChannelsPanel } from '@/components/panels/channels-panel'
|
| 34 |
+
import { DebugPanel } from '@/components/panels/debug-panel'
|
| 35 |
+
import { SecurityAuditPanel } from '@/components/panels/security-audit-panel'
|
| 36 |
+
import { NodesPanel } from '@/components/panels/nodes-panel'
|
| 37 |
+
import { ExecApprovalPanel } from '@/components/panels/exec-approval-panel'
|
| 38 |
+
import { ChatPagePanel } from '@/components/panels/chat-page-panel'
|
| 39 |
import { ChatPanel } from '@/components/chat/chat-panel'
|
| 40 |
+
import { getPluginPanel } from '@/lib/plugins'
|
| 41 |
import { ErrorBoundary } from '@/components/ErrorBoundary'
|
| 42 |
import { LocalModeBanner } from '@/components/layout/local-mode-banner'
|
| 43 |
import { UpdateBanner } from '@/components/layout/update-banner'
|
| 44 |
+
import { OpenClawUpdateBanner } from '@/components/layout/openclaw-update-banner'
|
| 45 |
+
import { OpenClawDoctorBanner } from '@/components/layout/openclaw-doctor-banner'
|
| 46 |
+
import { OnboardingWizard } from '@/components/onboarding/onboarding-wizard'
|
| 47 |
+
import { Loader } from '@/components/ui/loader'
|
| 48 |
+
import { ProjectManagerModal } from '@/components/modals/project-manager-modal'
|
| 49 |
+
import { ExecApprovalOverlay } from '@/components/modals/exec-approval-overlay'
|
| 50 |
import { useWebSocket } from '@/lib/websocket'
|
| 51 |
import { useServerEvents } from '@/lib/use-server-events'
|
| 52 |
+
import { completeNavigationTiming } from '@/lib/navigation-metrics'
|
| 53 |
+
import { panelHref, useNavigateToPanel } from '@/lib/navigation'
|
| 54 |
+
import { clearOnboardingDismissedThisSession, clearOnboardingReplayFromStart, getOnboardingSessionDecision, markOnboardingReplayFromStart, readOnboardingDismissedThisSession } from '@/lib/onboarding-session'
|
| 55 |
+
import { Button } from '@/components/ui/button'
|
| 56 |
import { useMissionControl } from '@/store'
|
| 57 |
|
| 58 |
+
interface GatewaySummary {
|
| 59 |
+
id: number
|
| 60 |
+
is_primary: number
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
function renderPluginPanel(panelId: string) {
|
| 64 |
+
const pluginPanel = getPluginPanel(panelId)
|
| 65 |
+
return pluginPanel ? createElement(pluginPanel) : <Dashboard />
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
function isLocalHost(hostname: string): boolean {
|
| 69 |
+
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1'
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
export default function Home() {
|
| 73 |
const router = useRouter()
|
| 74 |
const { connect } = useWebSocket()
|
| 75 |
+
const { activeTab, setActiveTab, setCurrentUser, setDashboardMode, setGatewayAvailable, setCapabilitiesChecked, setSubscription, setDefaultOrgName, setUpdateAvailable, setOpenclawUpdate, showOnboarding, setShowOnboarding, liveFeedOpen, toggleLiveFeed, showProjectManagerModal, setShowProjectManagerModal, fetchProjects, setChatPanelOpen, bootComplete, setBootComplete, setAgents, setSessions, setProjects, setInterfaceMode, setMemoryGraphAgents, setSkillsData } = useMissionControl()
|
| 76 |
|
| 77 |
// Sync URL → Zustand activeTab
|
| 78 |
const pathname = usePathname()
|
| 79 |
const panelFromUrl = pathname === '/' ? 'overview' : pathname.slice(1)
|
| 80 |
+
const normalizedPanel = panelFromUrl === 'sessions' ? 'chat' : panelFromUrl
|
| 81 |
|
| 82 |
useEffect(() => {
|
| 83 |
+
completeNavigationTiming(pathname)
|
| 84 |
+
}, [pathname])
|
| 85 |
+
|
| 86 |
+
useEffect(() => {
|
| 87 |
+
completeNavigationTiming(panelHref(activeTab))
|
| 88 |
+
}, [activeTab])
|
| 89 |
+
|
| 90 |
+
useEffect(() => {
|
| 91 |
+
setActiveTab(normalizedPanel)
|
| 92 |
+
if (normalizedPanel === 'chat') {
|
| 93 |
+
setChatPanelOpen(false)
|
| 94 |
+
}
|
| 95 |
+
if (panelFromUrl === 'sessions') {
|
| 96 |
+
router.replace('/chat')
|
| 97 |
+
}
|
| 98 |
+
}, [panelFromUrl, normalizedPanel, router, setActiveTab, setChatPanelOpen])
|
| 99 |
|
| 100 |
// Connect to SSE for real-time local DB events (tasks, agents, chat, etc.)
|
| 101 |
useServerEvents()
|
| 102 |
const [isClient, setIsClient] = useState(false)
|
| 103 |
+
const [initSteps, setInitSteps] = useState<Array<{ key: string; label: string; status: 'pending' | 'done' }>>([
|
| 104 |
+
{ key: 'auth', label: 'Authenticating operator', status: 'pending' },
|
| 105 |
+
{ key: 'capabilities', label: 'Detecting station mode', status: 'pending' },
|
| 106 |
+
{ key: 'config', label: 'Loading control config', status: 'pending' },
|
| 107 |
+
{ key: 'connect', label: 'Connecting runtime links', status: 'pending' },
|
| 108 |
+
{ key: 'agents', label: 'Syncing agent registry', status: 'pending' },
|
| 109 |
+
{ key: 'sessions', label: 'Loading active sessions', status: 'pending' },
|
| 110 |
+
{ key: 'projects', label: 'Hydrating workspace board', status: 'pending' },
|
| 111 |
+
{ key: 'memory', label: 'Mapping memory graph', status: 'pending' },
|
| 112 |
+
{ key: 'skills', label: 'Indexing skill catalog', status: 'pending' },
|
| 113 |
+
])
|
| 114 |
+
|
| 115 |
+
const markStep = (key: string) => {
|
| 116 |
+
setInitSteps(prev => prev.map(s => s.key === key ? { ...s, status: 'done' } : s))
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
useEffect(() => {
|
| 120 |
+
if (!bootComplete && initSteps.every(s => s.status === 'done')) {
|
| 121 |
+
const t = setTimeout(() => setBootComplete(), 400)
|
| 122 |
+
return () => clearTimeout(t)
|
| 123 |
+
}
|
| 124 |
+
}, [initSteps, bootComplete, setBootComplete])
|
| 125 |
+
|
| 126 |
+
// Security console warning (anti-self-XSS)
|
| 127 |
+
useEffect(() => {
|
| 128 |
+
if (!bootComplete) return
|
| 129 |
+
if (typeof window === 'undefined') return
|
| 130 |
+
const key = 'mc-console-warning'
|
| 131 |
+
if (sessionStorage.getItem(key)) return
|
| 132 |
+
sessionStorage.setItem(key, '1')
|
| 133 |
+
|
| 134 |
+
console.log(
|
| 135 |
+
'%c Stop! ',
|
| 136 |
+
'color: #fff; background: #e53e3e; font-size: 40px; font-weight: bold; padding: 4px 16px; border-radius: 4px;'
|
| 137 |
+
)
|
| 138 |
+
console.log(
|
| 139 |
+
'%cThis is a browser feature intended for developers.\n\nIf someone told you to copy-paste something here to enable a feature or "hack" an account, it is a scam and will give them access to your account.',
|
| 140 |
+
'font-size: 14px; color: #e2e8f0; padding: 8px 0;'
|
| 141 |
+
)
|
| 142 |
+
console.log(
|
| 143 |
+
'%cLearn more: https://en.wikipedia.org/wiki/Self-XSS',
|
| 144 |
+
'font-size: 12px; color: #718096;'
|
| 145 |
+
)
|
| 146 |
+
}, [bootComplete])
|
| 147 |
|
| 148 |
useEffect(() => {
|
| 149 |
setIsClient(true)
|
| 150 |
|
| 151 |
+
// OpenClaw control-ui device identity requires a secure browser context.
|
| 152 |
+
// Redirect remote HTTP sessions to HTTPS automatically to avoid handshake failures.
|
| 153 |
+
if (window.location.protocol === 'http:' && !isLocalHost(window.location.hostname)) {
|
| 154 |
+
const secureUrl = new URL(window.location.href)
|
| 155 |
+
secureUrl.protocol = 'https:'
|
| 156 |
+
window.location.replace(secureUrl.toString())
|
| 157 |
+
return
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
const connectWithEnvFallback = () => {
|
| 161 |
+
const explicitWsUrl = process.env.NEXT_PUBLIC_GATEWAY_URL || ''
|
| 162 |
+
const gatewayPort = process.env.NEXT_PUBLIC_GATEWAY_PORT || '18789'
|
| 163 |
+
const gatewayHost = process.env.NEXT_PUBLIC_GATEWAY_HOST || window.location.hostname
|
| 164 |
+
const gatewayProto =
|
| 165 |
+
process.env.NEXT_PUBLIC_GATEWAY_PROTOCOL ||
|
| 166 |
+
(window.location.protocol === 'https:' ? 'wss' : 'ws')
|
| 167 |
+
const wsUrl = explicitWsUrl || `${gatewayProto}://${gatewayHost}:${gatewayPort}`
|
| 168 |
+
connect(wsUrl)
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
const connectWithPrimaryGateway = async (): Promise<{ attempted: boolean; connected: boolean }> => {
|
| 172 |
+
try {
|
| 173 |
+
const gatewaysRes = await fetch('/api/gateways')
|
| 174 |
+
if (!gatewaysRes.ok) return { attempted: false, connected: false }
|
| 175 |
+
const gatewaysJson = await gatewaysRes.json().catch(() => ({}))
|
| 176 |
+
const gateways = Array.isArray(gatewaysJson?.gateways) ? gatewaysJson.gateways as GatewaySummary[] : []
|
| 177 |
+
if (gateways.length === 0) return { attempted: false, connected: false }
|
| 178 |
+
|
| 179 |
+
const primaryGateway = gateways.find(gw => Number(gw?.is_primary) === 1) || gateways[0]
|
| 180 |
+
if (!primaryGateway?.id) return { attempted: true, connected: false }
|
| 181 |
+
|
| 182 |
+
const connectRes = await fetch('/api/gateways/connect', {
|
| 183 |
+
method: 'POST',
|
| 184 |
+
headers: { 'Content-Type': 'application/json' },
|
| 185 |
+
body: JSON.stringify({ id: primaryGateway.id }),
|
| 186 |
+
})
|
| 187 |
+
if (!connectRes.ok) return { attempted: true, connected: false }
|
| 188 |
+
|
| 189 |
+
const payload = await connectRes.json().catch(() => ({}))
|
| 190 |
+
const wsUrl = typeof payload?.ws_url === 'string' ? payload.ws_url : ''
|
| 191 |
+
const wsToken = typeof payload?.token === 'string' ? payload.token : ''
|
| 192 |
+
if (!wsUrl) return { attempted: true, connected: false }
|
| 193 |
+
|
| 194 |
+
connect(wsUrl, wsToken)
|
| 195 |
+
return { attempted: true, connected: true }
|
| 196 |
+
} catch {
|
| 197 |
+
return { attempted: false, connected: false }
|
| 198 |
+
}
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
// Fetch current user
|
| 202 |
fetch('/api/auth/me')
|
| 203 |
.then(async (res) => {
|
|
|
|
| 207 |
}
|
| 208 |
return null
|
| 209 |
})
|
| 210 |
+
.then(data => { if (data?.user) setCurrentUser(data.user); markStep('auth') })
|
| 211 |
+
.catch(() => { markStep('auth') })
|
| 212 |
|
| 213 |
// Check for available updates
|
| 214 |
fetch('/api/releases/check')
|
|
|
|
| 224 |
})
|
| 225 |
.catch(() => {})
|
| 226 |
|
| 227 |
+
// Check for OpenClaw updates
|
| 228 |
+
fetch('/api/openclaw/version')
|
| 229 |
+
.then(res => res.ok ? res.json() : null)
|
| 230 |
+
.then(data => {
|
| 231 |
+
if (data?.updateAvailable) {
|
| 232 |
+
setOpenclawUpdate({
|
| 233 |
+
installed: data.installed,
|
| 234 |
+
latest: data.latest,
|
| 235 |
+
releaseUrl: data.releaseUrl,
|
| 236 |
+
releaseNotes: data.releaseNotes,
|
| 237 |
+
updateCommand: data.updateCommand,
|
| 238 |
+
})
|
| 239 |
+
}
|
| 240 |
+
})
|
| 241 |
+
.catch(() => {})
|
| 242 |
+
|
| 243 |
// Check capabilities, then conditionally connect to gateway
|
| 244 |
fetch('/api/status?action=capabilities')
|
| 245 |
.then(res => res.ok ? res.json() : null)
|
| 246 |
+
.then(async data => {
|
| 247 |
if (data?.subscription) {
|
| 248 |
setSubscription(data.subscription)
|
| 249 |
}
|
| 250 |
+
if (data?.processUser) {
|
| 251 |
+
setDefaultOrgName(data.processUser)
|
| 252 |
+
}
|
| 253 |
+
if (data?.interfaceMode === 'essential' || data?.interfaceMode === 'full') {
|
| 254 |
+
setInterfaceMode(data.interfaceMode)
|
| 255 |
+
}
|
| 256 |
if (data && data.gateway === false) {
|
| 257 |
setDashboardMode('local')
|
| 258 |
setGatewayAvailable(false)
|
| 259 |
+
setCapabilitiesChecked(true)
|
| 260 |
+
markStep('capabilities')
|
| 261 |
+
markStep('connect')
|
| 262 |
// Skip WebSocket connect — no gateway to talk to
|
| 263 |
return
|
| 264 |
}
|
|
|
|
| 266 |
setDashboardMode('full')
|
| 267 |
setGatewayAvailable(true)
|
| 268 |
}
|
| 269 |
+
setCapabilitiesChecked(true)
|
| 270 |
+
markStep('capabilities')
|
| 271 |
+
|
| 272 |
+
const primaryConnect = await connectWithPrimaryGateway()
|
| 273 |
+
if (!primaryConnect.connected && !primaryConnect.attempted) {
|
| 274 |
+
connectWithEnvFallback()
|
| 275 |
+
}
|
| 276 |
+
markStep('connect')
|
|
|
|
|
|
|
| 277 |
})
|
| 278 |
.catch(() => {
|
| 279 |
// If capabilities check fails, still try to connect
|
| 280 |
+
setCapabilitiesChecked(true)
|
| 281 |
+
markStep('capabilities')
|
| 282 |
+
markStep('connect')
|
| 283 |
+
connectWithEnvFallback()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
})
|
|
|
|
| 285 |
|
| 286 |
+
// Check onboarding state
|
| 287 |
+
fetch('/api/onboarding')
|
| 288 |
+
.then(res => res.ok ? res.json() : null)
|
| 289 |
+
.then(data => {
|
| 290 |
+
const decision = getOnboardingSessionDecision({
|
| 291 |
+
isAdmin: data?.isAdmin === true,
|
| 292 |
+
serverShowOnboarding: data?.showOnboarding === true,
|
| 293 |
+
completed: data?.completed === true,
|
| 294 |
+
skipped: data?.skipped === true,
|
| 295 |
+
dismissedThisSession: readOnboardingDismissedThisSession(),
|
| 296 |
+
})
|
| 297 |
+
|
| 298 |
+
if (decision.shouldOpen) {
|
| 299 |
+
clearOnboardingDismissedThisSession()
|
| 300 |
+
if (decision.replayFromStart) {
|
| 301 |
+
markOnboardingReplayFromStart()
|
| 302 |
+
} else {
|
| 303 |
+
clearOnboardingReplayFromStart()
|
| 304 |
+
}
|
| 305 |
+
setShowOnboarding(true)
|
| 306 |
+
}
|
| 307 |
+
markStep('config')
|
| 308 |
+
})
|
| 309 |
+
.catch(() => { markStep('config') })
|
| 310 |
+
// Preload workspace data in parallel
|
| 311 |
+
Promise.allSettled([
|
| 312 |
+
fetch('/api/agents')
|
| 313 |
+
.then(r => r.ok ? r.json() : null)
|
| 314 |
+
.then((agentsData) => {
|
| 315 |
+
if (agentsData?.agents) setAgents(agentsData.agents)
|
| 316 |
+
})
|
| 317 |
+
.finally(() => { markStep('agents') }),
|
| 318 |
+
fetch('/api/sessions')
|
| 319 |
+
.then(r => r.ok ? r.json() : null)
|
| 320 |
+
.then((sessionsData) => {
|
| 321 |
+
if (sessionsData?.sessions) setSessions(sessionsData.sessions)
|
| 322 |
+
})
|
| 323 |
+
.finally(() => { markStep('sessions') }),
|
| 324 |
+
fetch('/api/projects')
|
| 325 |
+
.then(r => r.ok ? r.json() : null)
|
| 326 |
+
.then((projectsData) => {
|
| 327 |
+
if (projectsData?.projects) setProjects(projectsData.projects)
|
| 328 |
+
})
|
| 329 |
+
.finally(() => { markStep('projects') }),
|
| 330 |
+
fetch('/api/memory/graph?agent=all')
|
| 331 |
+
.then(r => r.ok ? r.json() : null)
|
| 332 |
+
.then((graphData) => {
|
| 333 |
+
if (graphData?.agents) setMemoryGraphAgents(graphData.agents)
|
| 334 |
+
})
|
| 335 |
+
.finally(() => { markStep('memory') }),
|
| 336 |
+
fetch('/api/skills')
|
| 337 |
+
.then(r => r.ok ? r.json() : null)
|
| 338 |
+
.then((skillsData) => {
|
| 339 |
+
if (skillsData?.skills) setSkillsData(skillsData.skills, skillsData.groups || [], skillsData.total || 0)
|
| 340 |
+
})
|
| 341 |
+
.finally(() => { markStep('skills') }),
|
| 342 |
+
]).catch(() => { /* panels will lazy-load as fallback */ })
|
| 343 |
+
|
| 344 |
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- boot once on mount, not on every pathname change
|
| 345 |
+
}, [connect, router, setCurrentUser, setDashboardMode, setGatewayAvailable, setCapabilitiesChecked, setSubscription, setUpdateAvailable, setShowOnboarding, setAgents, setSessions, setProjects, setInterfaceMode, setMemoryGraphAgents, setSkillsData])
|
| 346 |
+
|
| 347 |
+
if (!isClient || !bootComplete) {
|
| 348 |
+
return <Loader variant="page" steps={isClient ? initSteps : undefined} />
|
| 349 |
}
|
| 350 |
|
| 351 |
return (
|
|
|
|
| 353 |
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:top-2 focus:left-2 focus:px-4 focus:py-2 focus:bg-primary focus:text-primary-foreground focus:rounded-md focus:text-sm focus:font-medium">
|
| 354 |
Skip to main content
|
| 355 |
</a>
|
| 356 |
+
|
| 357 |
{/* Left: Icon rail navigation (hidden on mobile, shown as bottom bar instead) */}
|
| 358 |
+
{!showOnboarding && <NavRail />}
|
| 359 |
|
| 360 |
{/* Center: Header + Content */}
|
| 361 |
<div className="flex-1 flex flex-col min-w-0">
|
| 362 |
+
{!showOnboarding && (
|
| 363 |
+
<>
|
| 364 |
+
<HeaderBar />
|
| 365 |
+
<LocalModeBanner />
|
| 366 |
+
<UpdateBanner />
|
| 367 |
+
<OpenClawUpdateBanner />
|
| 368 |
+
<OpenClawDoctorBanner />
|
| 369 |
+
</>
|
| 370 |
+
)}
|
| 371 |
+
<main
|
| 372 |
+
id="main-content"
|
| 373 |
+
className={`flex-1 overflow-auto pb-16 md:pb-0 ${showOnboarding ? 'pointer-events-none select-none blur-[2px] opacity-30' : ''}`}
|
| 374 |
+
role="main"
|
| 375 |
+
aria-hidden={showOnboarding}
|
| 376 |
+
>
|
| 377 |
+
<div aria-live="polite" className="flex flex-col min-h-full">
|
| 378 |
<ErrorBoundary key={activeTab}>
|
| 379 |
<ContentRouter tab={activeTab} />
|
| 380 |
</ErrorBoundary>
|
| 381 |
</div>
|
| 382 |
+
<footer className="px-4 pb-4 pt-2">
|
| 383 |
+
<p className="text-2xs text-muted-foreground/50 text-center">
|
| 384 |
+
Built with care by <a href="https://x.com/nyk_builderz" target="_blank" rel="noopener noreferrer" className="text-muted-foreground/70 hover:text-primary transition-colors duration-200">nyk</a>.
|
| 385 |
+
</p>
|
| 386 |
+
</footer>
|
| 387 |
</main>
|
| 388 |
</div>
|
| 389 |
|
| 390 |
{/* Right: Live feed (hidden on mobile) */}
|
| 391 |
+
{!showOnboarding && liveFeedOpen && (
|
| 392 |
<div className="hidden lg:flex h-full">
|
| 393 |
<LiveFeed />
|
| 394 |
</div>
|
| 395 |
)}
|
| 396 |
|
| 397 |
{/* Floating button to reopen LiveFeed when closed */}
|
| 398 |
+
{!showOnboarding && !liveFeedOpen && (
|
| 399 |
<button
|
| 400 |
onClick={toggleLiveFeed}
|
| 401 |
className="hidden lg:flex fixed right-0 top-1/2 -translate-y-1/2 z-30 w-6 h-12 items-center justify-center bg-card border border-r-0 border-border rounded-l-md text-muted-foreground hover:text-foreground hover:bg-secondary transition-all duration-200"
|
|
|
|
| 408 |
)}
|
| 409 |
|
| 410 |
{/* Chat panel overlay */}
|
| 411 |
+
{!showOnboarding && <ChatPanel />}
|
| 412 |
+
|
| 413 |
+
{/* Global exec approval overlay (shown regardless of active panel) */}
|
| 414 |
+
{!showOnboarding && <ExecApprovalOverlay />}
|
| 415 |
+
|
| 416 |
+
{/* Global Project Manager Modal */}
|
| 417 |
+
{!showOnboarding && showProjectManagerModal && (
|
| 418 |
+
<ProjectManagerModal
|
| 419 |
+
onClose={() => setShowProjectManagerModal(false)}
|
| 420 |
+
onChanged={async () => { await fetchProjects() }}
|
| 421 |
+
/>
|
| 422 |
+
)}
|
| 423 |
+
|
| 424 |
+
<OnboardingWizard />
|
| 425 |
</div>
|
| 426 |
)
|
| 427 |
}
|
| 428 |
|
| 429 |
+
const ESSENTIAL_PANELS = new Set([
|
| 430 |
+
'overview', 'agents', 'tasks', 'chat', 'activity', 'logs', 'settings',
|
| 431 |
+
])
|
| 432 |
+
|
| 433 |
function ContentRouter({ tab }: { tab: string }) {
|
| 434 |
+
const { dashboardMode, interfaceMode, setInterfaceMode } = useMissionControl()
|
| 435 |
+
const navigateToPanel = useNavigateToPanel()
|
| 436 |
const isLocal = dashboardMode === 'local'
|
| 437 |
|
| 438 |
+
// Guard: show nudge for non-essential panels in essential mode
|
| 439 |
+
if (interfaceMode === 'essential' && !ESSENTIAL_PANELS.has(tab)) {
|
| 440 |
+
return (
|
| 441 |
+
<div className="flex flex-col items-center justify-center py-24 text-center gap-4">
|
| 442 |
+
<p className="text-sm text-muted-foreground">
|
| 443 |
+
<span className="font-medium text-foreground capitalize">{tab.replace(/-/g, ' ')}</span> is available in Full mode.
|
| 444 |
+
</p>
|
| 445 |
+
<div className="flex items-center gap-2">
|
| 446 |
+
<Button
|
| 447 |
+
variant="outline"
|
| 448 |
+
size="sm"
|
| 449 |
+
onClick={async () => {
|
| 450 |
+
setInterfaceMode('full')
|
| 451 |
+
try { await fetch('/api/settings', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ settings: { 'general.interface_mode': 'full' } }) }) } catch {}
|
| 452 |
+
}}
|
| 453 |
+
>
|
| 454 |
+
Switch to Full
|
| 455 |
+
</Button>
|
| 456 |
+
<Button
|
| 457 |
+
variant="ghost"
|
| 458 |
+
size="sm"
|
| 459 |
+
onClick={() => navigateToPanel('overview')}
|
| 460 |
+
>
|
| 461 |
+
Go to Overview
|
| 462 |
+
</Button>
|
| 463 |
+
</div>
|
| 464 |
+
</div>
|
| 465 |
+
)
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
switch (tab) {
|
| 469 |
case 'overview':
|
| 470 |
return (
|
| 471 |
<>
|
| 472 |
<Dashboard />
|
| 473 |
{!isLocal && (
|
| 474 |
+
<div className="mt-4 mx-4 mb-4 rounded-lg border border-border bg-card overflow-hidden">
|
| 475 |
<AgentCommsPanel />
|
| 476 |
</div>
|
| 477 |
)}
|
|
|
|
| 483 |
return (
|
| 484 |
<>
|
| 485 |
<OrchestrationBar />
|
| 486 |
+
{isLocal && <LocalAgentsDocPanel />}
|
| 487 |
<AgentSquadPanelPhase3 />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
</>
|
| 489 |
)
|
|
|
|
|
|
|
| 490 |
case 'notifications':
|
| 491 |
return <NotificationsPanel />
|
| 492 |
case 'standup':
|
| 493 |
return <StandupPanel />
|
|
|
|
|
|
|
| 494 |
case 'sessions':
|
| 495 |
+
return <ChatPagePanel />
|
| 496 |
case 'logs':
|
| 497 |
return <LogViewerPanel />
|
| 498 |
case 'cron':
|
| 499 |
return <CronManagementPanel />
|
| 500 |
case 'memory':
|
| 501 |
return <MemoryBrowserPanel />
|
| 502 |
+
case 'cost-tracker':
|
| 503 |
case 'tokens':
|
|
|
|
| 504 |
case 'agent-costs':
|
| 505 |
+
return <CostTrackerPanel />
|
| 506 |
case 'users':
|
| 507 |
return <UserManagementPanel />
|
| 508 |
case 'history':
|
| 509 |
+
case 'activity':
|
| 510 |
+
return <ActivityFeedPanel />
|
| 511 |
case 'audit':
|
| 512 |
return <AuditTrailPanel />
|
| 513 |
case 'webhooks':
|
|
|
|
| 515 |
case 'alerts':
|
| 516 |
return <AlertRulesPanel />
|
| 517 |
case 'gateways':
|
| 518 |
+
if (isLocal) return <LocalModeUnavailable panel={tab} />
|
| 519 |
return <MultiGatewayPanel />
|
| 520 |
case 'gateway-config':
|
| 521 |
+
if (isLocal) return <LocalModeUnavailable panel={tab} />
|
| 522 |
return <GatewayConfigPanel />
|
| 523 |
case 'integrations':
|
| 524 |
return <IntegrationsPanel />
|
| 525 |
case 'settings':
|
| 526 |
return <SettingsPanel />
|
| 527 |
+
case 'super-admin':
|
| 528 |
+
return <SuperAdminPanel />
|
| 529 |
case 'github':
|
| 530 |
return <GitHubSyncPanel />
|
| 531 |
case 'office':
|
| 532 |
return <OfficePanel />
|
| 533 |
+
case 'skills':
|
| 534 |
+
return <SkillsPanel />
|
| 535 |
+
case 'channels':
|
| 536 |
+
if (isLocal) return <LocalModeUnavailable panel={tab} />
|
| 537 |
+
return <ChannelsPanel />
|
| 538 |
+
case 'nodes':
|
| 539 |
+
if (isLocal) return <LocalModeUnavailable panel={tab} />
|
| 540 |
+
return <NodesPanel />
|
| 541 |
+
case 'security':
|
| 542 |
+
return <SecurityAuditPanel />
|
| 543 |
+
case 'debug':
|
| 544 |
+
return <DebugPanel />
|
| 545 |
+
case 'exec-approvals':
|
| 546 |
+
if (isLocal) return <LocalModeUnavailable panel={tab} />
|
| 547 |
+
return <ExecApprovalPanel />
|
| 548 |
+
case 'chat':
|
| 549 |
+
return <ChatPagePanel />
|
| 550 |
+
default: {
|
| 551 |
+
return renderPluginPanel(tab)
|
| 552 |
+
}
|
| 553 |
}
|
| 554 |
}
|
| 555 |
+
|
| 556 |
+
function LocalModeUnavailable({ panel }: { panel: string }) {
|
| 557 |
+
return (
|
| 558 |
+
<div className="flex flex-col items-center justify-center py-24 text-center">
|
| 559 |
+
<p className="text-sm text-muted-foreground">
|
| 560 |
+
<span className="font-medium text-foreground">{panel}</span> requires an OpenClaw gateway connection.
|
| 561 |
+
</p>
|
| 562 |
+
<p className="text-xs text-muted-foreground mt-1">
|
| 563 |
+
Configure a gateway to enable this panel.
|
| 564 |
+
</p>
|
| 565 |
+
</div>
|
| 566 |
+
)
|
| 567 |
+
}
|
|
@@ -49,8 +49,14 @@ async function handleActivitiesRequest(request: NextRequest, workspaceId: number
|
|
| 49 |
const params: any[] = [workspaceId];
|
| 50 |
|
| 51 |
if (type) {
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
}
|
| 55 |
|
| 56 |
if (actor) {
|
|
@@ -132,8 +138,14 @@ async function handleActivitiesRequest(request: NextRequest, workspaceId: number
|
|
| 132 |
const countParams: any[] = [workspaceId];
|
| 133 |
|
| 134 |
if (type) {
|
| 135 |
-
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
}
|
| 138 |
|
| 139 |
if (actor) {
|
|
|
|
| 49 |
const params: any[] = [workspaceId];
|
| 50 |
|
| 51 |
if (type) {
|
| 52 |
+
const types = type.split(',').map(t => t.trim()).filter(Boolean);
|
| 53 |
+
if (types.length === 1) {
|
| 54 |
+
query += ' AND type = ?';
|
| 55 |
+
params.push(types[0]);
|
| 56 |
+
} else if (types.length > 1) {
|
| 57 |
+
query += ` AND type IN (${types.map(() => '?').join(',')})`;
|
| 58 |
+
params.push(...types);
|
| 59 |
+
}
|
| 60 |
}
|
| 61 |
|
| 62 |
if (actor) {
|
|
|
|
| 138 |
const countParams: any[] = [workspaceId];
|
| 139 |
|
| 140 |
if (type) {
|
| 141 |
+
const types = type.split(',').map(t => t.trim()).filter(Boolean);
|
| 142 |
+
if (types.length === 1) {
|
| 143 |
+
countQuery += ' AND type = ?';
|
| 144 |
+
countParams.push(types[0]);
|
| 145 |
+
} else if (types.length > 1) {
|
| 146 |
+
countQuery += ` AND type IN (${types.map(() => '?').join(',')})`;
|
| 147 |
+
countParams.push(...types);
|
| 148 |
+
}
|
| 149 |
}
|
| 150 |
|
| 151 |
if (actor) {
|
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from 'next/server'
|
| 2 |
+
import { requireRole } from '@/lib/auth'
|
| 3 |
+
import { getAdapter, listAdapters } from '@/lib/adapters'
|
| 4 |
+
import { agentHeartbeatLimiter } from '@/lib/rate-limit'
|
| 5 |
+
import { logger } from '@/lib/logger'
|
| 6 |
+
|
| 7 |
+
/**
|
| 8 |
+
* GET /api/adapters — List available framework adapters.
|
| 9 |
+
*/
|
| 10 |
+
export async function GET(request: NextRequest) {
|
| 11 |
+
const auth = requireRole(request, 'viewer')
|
| 12 |
+
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
| 13 |
+
|
| 14 |
+
return NextResponse.json({ adapters: listAdapters() })
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
/**
|
| 18 |
+
* POST /api/adapters — Framework-agnostic agent action dispatcher.
|
| 19 |
+
*
|
| 20 |
+
* Body: { framework, action, payload }
|
| 21 |
+
*
|
| 22 |
+
* Actions:
|
| 23 |
+
* register — Register an agent via its framework adapter
|
| 24 |
+
* heartbeat — Send a heartbeat/status update
|
| 25 |
+
* report — Report task progress
|
| 26 |
+
* assignments — Get pending task assignments
|
| 27 |
+
* disconnect — Disconnect an agent
|
| 28 |
+
*/
|
| 29 |
+
export async function POST(request: NextRequest) {
|
| 30 |
+
const auth = requireRole(request, 'operator')
|
| 31 |
+
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
| 32 |
+
|
| 33 |
+
const rateLimited = agentHeartbeatLimiter(request)
|
| 34 |
+
if (rateLimited) return rateLimited
|
| 35 |
+
|
| 36 |
+
let body: any
|
| 37 |
+
try {
|
| 38 |
+
body = await request.json()
|
| 39 |
+
} catch {
|
| 40 |
+
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
const framework = typeof body?.framework === 'string' ? body.framework.trim() : ''
|
| 44 |
+
const action = typeof body?.action === 'string' ? body.action.trim() : ''
|
| 45 |
+
const payload = body?.payload ?? {}
|
| 46 |
+
|
| 47 |
+
if (!framework || !action) {
|
| 48 |
+
return NextResponse.json({ error: 'framework and action are required' }, { status: 400 })
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
let adapter
|
| 52 |
+
try {
|
| 53 |
+
adapter = getAdapter(framework)
|
| 54 |
+
} catch {
|
| 55 |
+
return NextResponse.json({
|
| 56 |
+
error: `Unknown framework: ${framework}. Available: ${listAdapters().join(', ')}`,
|
| 57 |
+
}, { status: 400 })
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
try {
|
| 61 |
+
switch (action) {
|
| 62 |
+
case 'register': {
|
| 63 |
+
const { agentId, name, metadata } = payload
|
| 64 |
+
if (!agentId || !name) {
|
| 65 |
+
return NextResponse.json({ error: 'payload.agentId and payload.name required' }, { status: 400 })
|
| 66 |
+
}
|
| 67 |
+
await adapter.register({ agentId, name, framework, metadata })
|
| 68 |
+
return NextResponse.json({ ok: true, action: 'register', framework })
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
case 'heartbeat': {
|
| 72 |
+
const { agentId, status, metrics } = payload
|
| 73 |
+
if (!agentId) {
|
| 74 |
+
return NextResponse.json({ error: 'payload.agentId required' }, { status: 400 })
|
| 75 |
+
}
|
| 76 |
+
await adapter.heartbeat({ agentId, status: status || 'online', metrics })
|
| 77 |
+
return NextResponse.json({ ok: true, action: 'heartbeat', framework })
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
case 'report': {
|
| 81 |
+
const { taskId, agentId, progress, status: taskStatus, output } = payload
|
| 82 |
+
if (!taskId || !agentId) {
|
| 83 |
+
return NextResponse.json({ error: 'payload.taskId and payload.agentId required' }, { status: 400 })
|
| 84 |
+
}
|
| 85 |
+
await adapter.reportTask({ taskId, agentId, progress: progress ?? 0, status: taskStatus || 'in_progress', output })
|
| 86 |
+
return NextResponse.json({ ok: true, action: 'report', framework })
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
case 'assignments': {
|
| 90 |
+
const { agentId } = payload
|
| 91 |
+
if (!agentId) {
|
| 92 |
+
return NextResponse.json({ error: 'payload.agentId required' }, { status: 400 })
|
| 93 |
+
}
|
| 94 |
+
const assignments = await adapter.getAssignments(agentId)
|
| 95 |
+
return NextResponse.json({ assignments, framework })
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
case 'disconnect': {
|
| 99 |
+
const { agentId } = payload
|
| 100 |
+
if (!agentId) {
|
| 101 |
+
return NextResponse.json({ error: 'payload.agentId required' }, { status: 400 })
|
| 102 |
+
}
|
| 103 |
+
await adapter.disconnect(agentId)
|
| 104 |
+
return NextResponse.json({ ok: true, action: 'disconnect', framework })
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
default:
|
| 108 |
+
return NextResponse.json({
|
| 109 |
+
error: `Unknown action: ${action}. Use: register, heartbeat, report, assignments, disconnect`,
|
| 110 |
+
}, { status: 400 })
|
| 111 |
+
}
|
| 112 |
+
} catch (error) {
|
| 113 |
+
logger.error({ err: error, framework, action }, 'POST /api/adapters error')
|
| 114 |
+
return NextResponse.json({ error: 'Adapter action failed' }, { status: 500 })
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
export const dynamic = 'force-dynamic'
|
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from 'next/server'
|
| 2 |
+
import { getDatabase, db_helpers } from '@/lib/db'
|
| 3 |
+
import { requireRole } from '@/lib/auth'
|
| 4 |
+
import { config } from '@/lib/config'
|
| 5 |
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
| 6 |
+
import { dirname, isAbsolute, resolve } from 'node:path'
|
| 7 |
+
import { resolveWithin } from '@/lib/paths'
|
| 8 |
+
import { getAgentWorkspaceCandidates, readAgentWorkspaceFile } from '@/lib/agent-workspace'
|
| 9 |
+
import { logger } from '@/lib/logger'
|
| 10 |
+
|
| 11 |
+
const ALLOWED_FILES = new Set([
|
| 12 |
+
'agent.md',
|
| 13 |
+
'identity.md',
|
| 14 |
+
'soul.md',
|
| 15 |
+
'WORKING.md',
|
| 16 |
+
'MEMORY.md',
|
| 17 |
+
'TOOLS.md',
|
| 18 |
+
'AGENTS.md',
|
| 19 |
+
'MISSION.md',
|
| 20 |
+
'USER.md',
|
| 21 |
+
])
|
| 22 |
+
const FILE_ALIASES: Record<string, string[]> = {
|
| 23 |
+
'agent.md': ['agent.md', 'AGENT.md', 'MISSION.md', 'USER.md'],
|
| 24 |
+
'identity.md': ['identity.md', 'IDENTITY.md'],
|
| 25 |
+
'soul.md': ['soul.md', 'SOUL.md'],
|
| 26 |
+
'WORKING.md': ['WORKING.md', 'working.md'],
|
| 27 |
+
'MEMORY.md': ['MEMORY.md', 'memory.md'],
|
| 28 |
+
'TOOLS.md': ['TOOLS.md', 'tools.md'],
|
| 29 |
+
'AGENTS.md': ['AGENTS.md', 'agents.md'],
|
| 30 |
+
'MISSION.md': ['MISSION.md', 'mission.md'],
|
| 31 |
+
'USER.md': ['USER.md', 'user.md'],
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
function resolveAgentWorkspacePath(workspace: string): string {
|
| 35 |
+
if (isAbsolute(workspace)) return resolve(workspace)
|
| 36 |
+
if (!config.openclawStateDir) throw new Error('OPENCLAW_STATE_DIR not configured')
|
| 37 |
+
return resolveWithin(config.openclawStateDir, workspace)
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
function getAgentByIdOrName(db: ReturnType<typeof getDatabase>, id: string, workspaceId: number): any | undefined {
|
| 41 |
+
if (isNaN(Number(id))) {
|
| 42 |
+
return db.prepare('SELECT * FROM agents WHERE name = ? AND workspace_id = ?').get(id, workspaceId)
|
| 43 |
+
}
|
| 44 |
+
return db.prepare('SELECT * FROM agents WHERE id = ? AND workspace_id = ?').get(Number(id), workspaceId)
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
export async function GET(
|
| 48 |
+
request: NextRequest,
|
| 49 |
+
{ params }: { params: Promise<{ id: string }> }
|
| 50 |
+
) {
|
| 51 |
+
const auth = requireRole(request, 'viewer')
|
| 52 |
+
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
| 53 |
+
|
| 54 |
+
try {
|
| 55 |
+
const { id } = await params
|
| 56 |
+
const db = getDatabase()
|
| 57 |
+
const workspaceId = auth.user.workspace_id ?? 1
|
| 58 |
+
const agent = getAgentByIdOrName(db, id, workspaceId)
|
| 59 |
+
if (!agent) return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
|
| 60 |
+
|
| 61 |
+
const agentConfig = agent.config ? JSON.parse(agent.config) : {}
|
| 62 |
+
const candidates = getAgentWorkspaceCandidates(agentConfig, agent.name)
|
| 63 |
+
if (candidates.length === 0) {
|
| 64 |
+
return NextResponse.json({ error: 'Agent workspace is not configured' }, { status: 400 })
|
| 65 |
+
}
|
| 66 |
+
const safeWorkspace = candidates[0]
|
| 67 |
+
const requested = (new URL(request.url).searchParams.get('file') || '').trim()
|
| 68 |
+
const files = requested
|
| 69 |
+
? [requested]
|
| 70 |
+
: ['agent.md', 'identity.md', 'soul.md', 'WORKING.md', 'MEMORY.md', 'TOOLS.md', 'AGENTS.md', 'MISSION.md', 'USER.md']
|
| 71 |
+
|
| 72 |
+
const payload: Record<string, { exists: boolean; content: string }> = {}
|
| 73 |
+
for (const file of files) {
|
| 74 |
+
if (!ALLOWED_FILES.has(file)) {
|
| 75 |
+
return NextResponse.json({ error: `Unsupported file: ${file}` }, { status: 400 })
|
| 76 |
+
}
|
| 77 |
+
const aliases = FILE_ALIASES[file] || [file]
|
| 78 |
+
const match = readAgentWorkspaceFile(candidates, aliases)
|
| 79 |
+
payload[file] = { exists: match.exists, content: match.content }
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
return NextResponse.json({
|
| 83 |
+
agent: { id: agent.id, name: agent.name },
|
| 84 |
+
workspace: safeWorkspace,
|
| 85 |
+
files: payload,
|
| 86 |
+
})
|
| 87 |
+
} catch (error) {
|
| 88 |
+
logger.error({ err: error }, 'GET /api/agents/[id]/files error')
|
| 89 |
+
return NextResponse.json({ error: 'Failed to load workspace files' }, { status: 500 })
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
export async function PUT(
|
| 94 |
+
request: NextRequest,
|
| 95 |
+
{ params }: { params: Promise<{ id: string }> }
|
| 96 |
+
) {
|
| 97 |
+
const auth = requireRole(request, 'operator')
|
| 98 |
+
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
| 99 |
+
|
| 100 |
+
try {
|
| 101 |
+
const { id } = await params
|
| 102 |
+
const body = await request.json()
|
| 103 |
+
const file = String(body?.file || '').trim()
|
| 104 |
+
const content = String(body?.content || '')
|
| 105 |
+
const MAX_WORKSPACE_FILE_SIZE = 1024 * 1024 // 1 MB
|
| 106 |
+
if (content.length > MAX_WORKSPACE_FILE_SIZE) {
|
| 107 |
+
return NextResponse.json({ error: `File content too large (max ${MAX_WORKSPACE_FILE_SIZE} bytes)` }, { status: 413 })
|
| 108 |
+
}
|
| 109 |
+
if (!ALLOWED_FILES.has(file)) {
|
| 110 |
+
return NextResponse.json({ error: `Unsupported file: ${file}` }, { status: 400 })
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
const db = getDatabase()
|
| 114 |
+
const workspaceId = auth.user.workspace_id ?? 1
|
| 115 |
+
const agent = getAgentByIdOrName(db, id, workspaceId)
|
| 116 |
+
if (!agent) return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
|
| 117 |
+
|
| 118 |
+
const agentConfig = agent.config ? JSON.parse(agent.config) : {}
|
| 119 |
+
const candidates = getAgentWorkspaceCandidates(agentConfig, agent.name)
|
| 120 |
+
const safeWorkspace = candidates[0]
|
| 121 |
+
if (!safeWorkspace) {
|
| 122 |
+
return NextResponse.json({ error: 'Agent workspace is not configured' }, { status: 400 })
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
const safePath = resolveWithin(safeWorkspace, file)
|
| 126 |
+
mkdirSync(dirname(safePath), { recursive: true })
|
| 127 |
+
writeFileSync(safePath, content, 'utf-8')
|
| 128 |
+
|
| 129 |
+
if (file === 'soul.md') {
|
| 130 |
+
db.prepare('UPDATE agents SET soul_content = ?, updated_at = unixepoch() WHERE id = ? AND workspace_id = ?')
|
| 131 |
+
.run(content, agent.id, workspaceId)
|
| 132 |
+
}
|
| 133 |
+
if (file === 'WORKING.md') {
|
| 134 |
+
db.prepare('UPDATE agents SET working_memory = ?, updated_at = unixepoch() WHERE id = ? AND workspace_id = ?')
|
| 135 |
+
.run(content, agent.id, workspaceId)
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
db_helpers.logActivity(
|
| 139 |
+
'agent_workspace_file_updated',
|
| 140 |
+
'agent',
|
| 141 |
+
agent.id,
|
| 142 |
+
auth.user.username,
|
| 143 |
+
`${file} updated for ${agent.name}`,
|
| 144 |
+
{ file, size: content.length },
|
| 145 |
+
workspaceId
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
return NextResponse.json({ success: true, file, size: content.length })
|
| 149 |
+
} catch (error) {
|
| 150 |
+
logger.error({ err: error }, 'PUT /api/agents/[id]/files error')
|
| 151 |
+
return NextResponse.json({ error: 'Failed to save workspace file' }, { status: 500 })
|
| 152 |
+
}
|
| 153 |
+
}
|
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import { NextRequest, NextResponse } from 'next/server';
|
| 2 |
import { getDatabase, db_helpers } from '@/lib/db';
|
| 3 |
import { requireRole } from '@/lib/auth';
|
|
|
|
| 4 |
import { logger } from '@/lib/logger';
|
| 5 |
|
| 6 |
/**
|
|
@@ -189,6 +190,9 @@ export async function POST(
|
|
| 189 |
const auth = requireRole(request, 'operator');
|
| 190 |
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status });
|
| 191 |
|
|
|
|
|
|
|
|
|
|
| 192 |
let body: any = {};
|
| 193 |
try {
|
| 194 |
body = await request.json();
|
|
|
|
| 1 |
import { NextRequest, NextResponse } from 'next/server';
|
| 2 |
import { getDatabase, db_helpers } from '@/lib/db';
|
| 3 |
import { requireRole } from '@/lib/auth';
|
| 4 |
+
import { agentHeartbeatLimiter } from '@/lib/rate-limit';
|
| 5 |
import { logger } from '@/lib/logger';
|
| 6 |
|
| 7 |
/**
|
|
|
|
| 190 |
const auth = requireRole(request, 'operator');
|
| 191 |
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status });
|
| 192 |
|
| 193 |
+
const rateLimited = agentHeartbeatLimiter(request);
|
| 194 |
+
if (rateLimited) return rateLimited;
|
| 195 |
+
|
| 196 |
let body: any = {};
|
| 197 |
try {
|
| 198 |
body = await request.json();
|
|
@@ -2,6 +2,17 @@ import { NextRequest, NextResponse } from 'next/server';
|
|
| 2 |
import { getDatabase, db_helpers } from '@/lib/db';
|
| 3 |
import { requireRole } from '@/lib/auth';
|
| 4 |
import { logger } from '@/lib/logger';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
/**
|
| 7 |
* GET /api/agents/[id]/memory - Get agent's working memory
|
|
@@ -43,11 +54,28 @@ export async function GET(
|
|
| 43 |
db.exec("ALTER TABLE agents ADD COLUMN working_memory TEXT DEFAULT ''");
|
| 44 |
}
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
// Get working memory content
|
| 47 |
const memoryStmt = db.prepare(`SELECT working_memory FROM agents WHERE ${isNaN(Number(agentId)) ? 'name' : 'id'} = ? AND workspace_id = ?`);
|
| 48 |
const result = memoryStmt.get(agentId, workspaceId) as any;
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
| 51 |
|
| 52 |
return NextResponse.json({
|
| 53 |
agent: {
|
|
@@ -56,6 +84,7 @@ export async function GET(
|
|
| 56 |
role: agent.role
|
| 57 |
},
|
| 58 |
working_memory: workingMemory,
|
|
|
|
| 59 |
updated_at: agent.updated_at,
|
| 60 |
size: workingMemory.length
|
| 61 |
});
|
|
@@ -118,6 +147,22 @@ export async function PUT(
|
|
| 118 |
}
|
| 119 |
|
| 120 |
const now = Math.floor(Date.now() / 1000);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
// Update working memory
|
| 123 |
const updateStmt = db.prepare(`
|
|
@@ -135,10 +180,11 @@ export async function PUT(
|
|
| 135 |
agent.id,
|
| 136 |
agent.name,
|
| 137 |
`Working memory ${append ? 'appended' : 'updated'} for agent ${agent.name}`,
|
| 138 |
-
{
|
| 139 |
content_length: newContent.length,
|
| 140 |
append_mode: append || false,
|
| 141 |
-
timestamp: now
|
|
|
|
| 142 |
},
|
| 143 |
workspaceId
|
| 144 |
);
|
|
@@ -147,6 +193,7 @@ export async function PUT(
|
|
| 147 |
success: true,
|
| 148 |
message: `Working memory ${append ? 'appended' : 'updated'} for ${agent.name}`,
|
| 149 |
working_memory: newContent,
|
|
|
|
| 150 |
updated_at: now,
|
| 151 |
size: newContent.length
|
| 152 |
});
|
|
@@ -185,6 +232,20 @@ export async function DELETE(
|
|
| 185 |
}
|
| 186 |
|
| 187 |
const now = Math.floor(Date.now() / 1000);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
|
| 189 |
// Clear working memory
|
| 190 |
const updateStmt = db.prepare(`
|
|
|
|
| 2 |
import { getDatabase, db_helpers } from '@/lib/db';
|
| 3 |
import { requireRole } from '@/lib/auth';
|
| 4 |
import { logger } from '@/lib/logger';
|
| 5 |
+
import { config } from '@/lib/config';
|
| 6 |
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
| 7 |
+
import { dirname, isAbsolute, resolve } from 'node:path';
|
| 8 |
+
import { resolveWithin } from '@/lib/paths';
|
| 9 |
+
import { getAgentWorkspaceCandidates, readAgentWorkspaceFile } from '@/lib/agent-workspace';
|
| 10 |
+
|
| 11 |
+
function resolveAgentWorkspacePath(workspace: string): string {
|
| 12 |
+
if (isAbsolute(workspace)) return resolve(workspace)
|
| 13 |
+
if (!config.openclawStateDir) throw new Error('OPENCLAW_STATE_DIR not configured')
|
| 14 |
+
return resolveWithin(config.openclawStateDir, workspace)
|
| 15 |
+
}
|
| 16 |
|
| 17 |
/**
|
| 18 |
* GET /api/agents/[id]/memory - Get agent's working memory
|
|
|
|
| 54 |
db.exec("ALTER TABLE agents ADD COLUMN working_memory TEXT DEFAULT ''");
|
| 55 |
}
|
| 56 |
|
| 57 |
+
// Prefer workspace WORKING.md, fall back to DB working_memory
|
| 58 |
+
let workingMemory = '';
|
| 59 |
+
let source: 'workspace' | 'database' | 'none' = 'none';
|
| 60 |
+
try {
|
| 61 |
+
const agentConfig = agent.config ? JSON.parse(agent.config) : {};
|
| 62 |
+
const candidates = getAgentWorkspaceCandidates(agentConfig, agent.name);
|
| 63 |
+
const match = readAgentWorkspaceFile(candidates, ['WORKING.md', 'working.md', 'MEMORY.md', 'memory.md']);
|
| 64 |
+
if (match.exists) {
|
| 65 |
+
workingMemory = match.content;
|
| 66 |
+
source = 'workspace';
|
| 67 |
+
}
|
| 68 |
+
} catch (err) {
|
| 69 |
+
logger.warn({ err, agent: agent.name }, 'Failed to read WORKING.md from workspace');
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
// Get working memory content
|
| 73 |
const memoryStmt = db.prepare(`SELECT working_memory FROM agents WHERE ${isNaN(Number(agentId)) ? 'name' : 'id'} = ? AND workspace_id = ?`);
|
| 74 |
const result = memoryStmt.get(agentId, workspaceId) as any;
|
| 75 |
+
if (!workingMemory) {
|
| 76 |
+
workingMemory = result?.working_memory || '';
|
| 77 |
+
source = workingMemory ? 'database' : 'none';
|
| 78 |
+
}
|
| 79 |
|
| 80 |
return NextResponse.json({
|
| 81 |
agent: {
|
|
|
|
| 84 |
role: agent.role
|
| 85 |
},
|
| 86 |
working_memory: workingMemory,
|
| 87 |
+
source,
|
| 88 |
updated_at: agent.updated_at,
|
| 89 |
size: workingMemory.length
|
| 90 |
});
|
|
|
|
| 147 |
}
|
| 148 |
|
| 149 |
const now = Math.floor(Date.now() / 1000);
|
| 150 |
+
|
| 151 |
+
// Best effort: sync workspace WORKING.md if agent workspace is configured
|
| 152 |
+
let savedToWorkspace = false;
|
| 153 |
+
try {
|
| 154 |
+
const agentConfig = agent.config ? JSON.parse(agent.config) : {};
|
| 155 |
+
const candidates = getAgentWorkspaceCandidates(agentConfig, agent.name);
|
| 156 |
+
const safeWorkspace = candidates[0];
|
| 157 |
+
if (safeWorkspace) {
|
| 158 |
+
const safeWorkingPath = resolveWithin(safeWorkspace, 'WORKING.md');
|
| 159 |
+
mkdirSync(dirname(safeWorkingPath), { recursive: true });
|
| 160 |
+
writeFileSync(safeWorkingPath, newContent, 'utf-8');
|
| 161 |
+
savedToWorkspace = true;
|
| 162 |
+
}
|
| 163 |
+
} catch (err) {
|
| 164 |
+
logger.warn({ err, agent: agent.name }, 'Failed to write WORKING.md to workspace');
|
| 165 |
+
}
|
| 166 |
|
| 167 |
// Update working memory
|
| 168 |
const updateStmt = db.prepare(`
|
|
|
|
| 180 |
agent.id,
|
| 181 |
agent.name,
|
| 182 |
`Working memory ${append ? 'appended' : 'updated'} for agent ${agent.name}`,
|
| 183 |
+
{
|
| 184 |
content_length: newContent.length,
|
| 185 |
append_mode: append || false,
|
| 186 |
+
timestamp: now,
|
| 187 |
+
saved_to_workspace: savedToWorkspace
|
| 188 |
},
|
| 189 |
workspaceId
|
| 190 |
);
|
|
|
|
| 193 |
success: true,
|
| 194 |
message: `Working memory ${append ? 'appended' : 'updated'} for ${agent.name}`,
|
| 195 |
working_memory: newContent,
|
| 196 |
+
saved_to_workspace: savedToWorkspace,
|
| 197 |
updated_at: now,
|
| 198 |
size: newContent.length
|
| 199 |
});
|
|
|
|
| 232 |
}
|
| 233 |
|
| 234 |
const now = Math.floor(Date.now() / 1000);
|
| 235 |
+
|
| 236 |
+
// Best effort: clear workspace WORKING.md if agent workspace is configured
|
| 237 |
+
try {
|
| 238 |
+
const agentConfig = agent.config ? JSON.parse(agent.config) : {};
|
| 239 |
+
const candidates = getAgentWorkspaceCandidates(agentConfig, agent.name);
|
| 240 |
+
const safeWorkspace = candidates[0];
|
| 241 |
+
if (safeWorkspace) {
|
| 242 |
+
const safeWorkingPath = resolveWithin(safeWorkspace, 'WORKING.md');
|
| 243 |
+
mkdirSync(dirname(safeWorkingPath), { recursive: true });
|
| 244 |
+
writeFileSync(safeWorkingPath, '', 'utf-8');
|
| 245 |
+
}
|
| 246 |
+
} catch (err) {
|
| 247 |
+
logger.warn({ err, agent: agent.name }, 'Failed to clear WORKING.md in workspace');
|
| 248 |
+
}
|
| 249 |
|
| 250 |
// Clear working memory
|
| 251 |
const updateStmt = db.prepare(`
|
|
@@ -1,9 +1,10 @@
|
|
| 1 |
import { NextRequest, NextResponse } from 'next/server'
|
| 2 |
import { getDatabase, db_helpers, logAuditEvent } from '@/lib/db'
|
| 3 |
import { requireRole } from '@/lib/auth'
|
| 4 |
-
import { writeAgentToConfig, enrichAgentConfigFromWorkspace } from '@/lib/agent-sync'
|
| 5 |
import { eventBus } from '@/lib/event-bus'
|
| 6 |
import { logger } from '@/lib/logger'
|
|
|
|
| 7 |
|
| 8 |
/**
|
| 9 |
* GET /api/agents/[id] - Get a single agent by ID or name
|
|
@@ -102,20 +103,9 @@ export async function PUT(
|
|
| 102 |
return writeBack
|
| 103 |
}
|
| 104 |
|
| 105 |
-
// Unified save:
|
| 106 |
-
|
| 107 |
-
try {
|
| 108 |
-
await writeAgentToConfig(getWriteBackPayload(gateway_config))
|
| 109 |
-
} catch (err: any) {
|
| 110 |
-
return NextResponse.json(
|
| 111 |
-
{ error: `Save failed: unable to update gateway config: ${err.message}` },
|
| 112 |
-
{ status: 502 }
|
| 113 |
-
)
|
| 114 |
-
}
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
try {
|
| 118 |
-
// Build update
|
| 119 |
const fields: string[] = ['updated_at = ?']
|
| 120 |
const values: any[] = [now]
|
| 121 |
|
|
@@ -132,19 +122,31 @@ export async function PUT(
|
|
| 132 |
values.push(agent.id, workspaceId)
|
| 133 |
db.prepare(`UPDATE agents SET ${fields.join(', ')} WHERE id = ? AND workspace_id = ?`).run(...values)
|
| 134 |
} catch (err: any) {
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
try {
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
)
|
|
|
|
|
|
|
| 145 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
}
|
| 147 |
-
return NextResponse.json({ error: `Save failed: ${err.message}` }, { status: 500 })
|
| 148 |
}
|
| 149 |
|
| 150 |
if (shouldWriteToGateway) {
|
|
@@ -205,6 +207,13 @@ export async function DELETE(
|
|
| 205 |
const db = getDatabase()
|
| 206 |
const { id } = await params
|
| 207 |
const workspaceId = auth.user.workspace_id ?? 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
|
| 209 |
let agent
|
| 210 |
if (isNaN(Number(id))) {
|
|
@@ -217,6 +226,38 @@ export async function DELETE(
|
|
| 217 |
return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
|
| 218 |
}
|
| 219 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
db.prepare('DELETE FROM agents WHERE id = ? AND workspace_id = ?').run(agent.id, workspaceId)
|
| 221 |
|
| 222 |
db_helpers.logActivity(
|
|
@@ -225,13 +266,18 @@ export async function DELETE(
|
|
| 225 |
agent.id,
|
| 226 |
auth.user.username,
|
| 227 |
`Deleted agent: ${agent.name}`,
|
| 228 |
-
{ name: agent.name, role: agent.role },
|
| 229 |
workspaceId
|
| 230 |
)
|
| 231 |
|
| 232 |
eventBus.broadcast('agent.deleted', { id: agent.id, name: agent.name })
|
| 233 |
|
| 234 |
-
return NextResponse.json({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
} catch (error) {
|
| 236 |
logger.error({ err: error }, 'DELETE /api/agents/[id] error')
|
| 237 |
return NextResponse.json({ error: 'Failed to delete agent' }, { status: 500 })
|
|
|
|
| 1 |
import { NextRequest, NextResponse } from 'next/server'
|
| 2 |
import { getDatabase, db_helpers, logAuditEvent } from '@/lib/db'
|
| 3 |
import { requireRole } from '@/lib/auth'
|
| 4 |
+
import { writeAgentToConfig, enrichAgentConfigFromWorkspace, removeAgentFromConfig } from '@/lib/agent-sync'
|
| 5 |
import { eventBus } from '@/lib/event-bus'
|
| 6 |
import { logger } from '@/lib/logger'
|
| 7 |
+
import { runOpenClaw } from '@/lib/command'
|
| 8 |
|
| 9 |
/**
|
| 10 |
* GET /api/agents/[id] - Get a single agent by ID or name
|
|
|
|
| 103 |
return writeBack
|
| 104 |
}
|
| 105 |
|
| 106 |
+
// Unified save: DB first (transactional, easy to revert), then gateway file.
|
| 107 |
+
// If gateway write fails after DB succeeds, revert DB to keep consistency.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
try {
|
|
|
|
| 109 |
const fields: string[] = ['updated_at = ?']
|
| 110 |
const values: any[] = [now]
|
| 111 |
|
|
|
|
| 122 |
values.push(agent.id, workspaceId)
|
| 123 |
db.prepare(`UPDATE agents SET ${fields.join(', ')} WHERE id = ? AND workspace_id = ?`).run(...values)
|
| 124 |
} catch (err: any) {
|
| 125 |
+
return NextResponse.json({ error: `Save failed: ${err.message}` }, { status: 500 })
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
if (shouldWriteToGateway) {
|
| 129 |
+
try {
|
| 130 |
+
await writeAgentToConfig(getWriteBackPayload(gateway_config))
|
| 131 |
+
} catch (err: any) {
|
| 132 |
+
// Gateway write failed — revert DB to previous state
|
| 133 |
try {
|
| 134 |
+
const revertFields: string[] = ['updated_at = ?']
|
| 135 |
+
const revertValues: any[] = [agent.updated_at]
|
| 136 |
+
revertFields.push('role = ?')
|
| 137 |
+
revertValues.push(agent.role)
|
| 138 |
+
revertFields.push('config = ?')
|
| 139 |
+
revertValues.push(agent.config || '{}')
|
| 140 |
+
revertValues.push(agent.id, workspaceId)
|
| 141 |
+
db.prepare(`UPDATE agents SET ${revertFields.join(', ')} WHERE id = ? AND workspace_id = ?`).run(...revertValues)
|
| 142 |
+
} catch (revertErr: any) {
|
| 143 |
+
logger.error({ err: revertErr, agent: agent.name }, 'Failed to revert DB after gateway write failure')
|
| 144 |
}
|
| 145 |
+
return NextResponse.json(
|
| 146 |
+
{ error: `Save failed: unable to update gateway config: ${err.message}` },
|
| 147 |
+
{ status: 502 }
|
| 148 |
+
)
|
| 149 |
}
|
|
|
|
| 150 |
}
|
| 151 |
|
| 152 |
if (shouldWriteToGateway) {
|
|
|
|
| 207 |
const db = getDatabase()
|
| 208 |
const { id } = await params
|
| 209 |
const workspaceId = auth.user.workspace_id ?? 1;
|
| 210 |
+
let removeWorkspace = false
|
| 211 |
+
try {
|
| 212 |
+
const body = await request.json()
|
| 213 |
+
removeWorkspace = Boolean(body?.remove_workspace)
|
| 214 |
+
} catch {
|
| 215 |
+
// Optional body
|
| 216 |
+
}
|
| 217 |
|
| 218 |
let agent
|
| 219 |
if (isNaN(Number(id))) {
|
|
|
|
| 226 |
return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
|
| 227 |
}
|
| 228 |
|
| 229 |
+
if (removeWorkspace) {
|
| 230 |
+
const agentConfig = agent.config ? JSON.parse(agent.config) : {}
|
| 231 |
+
const openclawId =
|
| 232 |
+
String(agentConfig?.openclawId || agent.name || '')
|
| 233 |
+
.toLowerCase()
|
| 234 |
+
.replace(/[^a-z0-9._-]+/g, '-')
|
| 235 |
+
.replace(/^-+|-+$/g, '') || agent.name
|
| 236 |
+
try {
|
| 237 |
+
await runOpenClaw(['agents', 'delete', openclawId, '--force'], { timeoutMs: 30000 })
|
| 238 |
+
} catch (err: any) {
|
| 239 |
+
logger.error({ err, openclawId, agent: agent.name }, 'Failed to remove OpenClaw agent/workspace')
|
| 240 |
+
return NextResponse.json(
|
| 241 |
+
{ error: `Failed to remove OpenClaw workspace for ${agent.name}: ${err?.message || 'unknown error'}` },
|
| 242 |
+
{ status: 502 }
|
| 243 |
+
)
|
| 244 |
+
}
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
let configCleanupWarning: string | null = null
|
| 248 |
+
try {
|
| 249 |
+
const agentConfig = agent.config ? JSON.parse(agent.config) : {}
|
| 250 |
+
const openclawId =
|
| 251 |
+
String(agentConfig?.openclawId || agent.name || '')
|
| 252 |
+
.toLowerCase()
|
| 253 |
+
.replace(/[^a-z0-9._-]+/g, '-')
|
| 254 |
+
.replace(/^-+|-+$/g, '') || agent.name
|
| 255 |
+
await removeAgentFromConfig({ id: openclawId, name: agent.name })
|
| 256 |
+
} catch (err: any) {
|
| 257 |
+
configCleanupWarning = `OpenClaw config cleanup skipped for ${agent.name}: ${err?.message || 'unknown error'}`
|
| 258 |
+
logger.warn({ err, agent: agent.name }, 'Failed to remove OpenClaw agent config entry')
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
db.prepare('DELETE FROM agents WHERE id = ? AND workspace_id = ?').run(agent.id, workspaceId)
|
| 262 |
|
| 263 |
db_helpers.logActivity(
|
|
|
|
| 266 |
agent.id,
|
| 267 |
auth.user.username,
|
| 268 |
`Deleted agent: ${agent.name}`,
|
| 269 |
+
{ name: agent.name, role: agent.role, remove_workspace: removeWorkspace },
|
| 270 |
workspaceId
|
| 271 |
)
|
| 272 |
|
| 273 |
eventBus.broadcast('agent.deleted', { id: agent.id, name: agent.name })
|
| 274 |
|
| 275 |
+
return NextResponse.json({
|
| 276 |
+
success: true,
|
| 277 |
+
deleted: agent.name,
|
| 278 |
+
remove_workspace: removeWorkspace,
|
| 279 |
+
...(configCleanupWarning ? { warning: configCleanupWarning } : {}),
|
| 280 |
+
})
|
| 281 |
} catch (error) {
|
| 282 |
logger.error({ err: error }, 'DELETE /api/agents/[id] error')
|
| 283 |
return NextResponse.json({ error: 'Failed to delete agent' }, { status: 500 })
|
|
@@ -4,6 +4,7 @@ import { readFileSync, existsSync, readdirSync, writeFileSync, mkdirSync } from
|
|
| 4 |
import { join, dirname, isAbsolute, resolve } from 'path';
|
| 5 |
import { config } from '@/lib/config';
|
| 6 |
import { resolveWithin } from '@/lib/paths';
|
|
|
|
| 7 |
import { requireRole } from '@/lib/auth';
|
| 8 |
import { logger } from '@/lib/logger';
|
| 9 |
|
|
@@ -47,13 +48,11 @@ export async function GET(
|
|
| 47 |
|
| 48 |
try {
|
| 49 |
const agentConfig = agent.config ? JSON.parse(agent.config) : {}
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
source = 'workspace'
|
| 56 |
-
}
|
| 57 |
}
|
| 58 |
} catch (err) {
|
| 59 |
logger.warn({ err, agent: agent.name }, 'Failed to read soul.md from workspace')
|
|
@@ -163,8 +162,9 @@ export async function PUT(
|
|
| 163 |
let savedToWorkspace = false
|
| 164 |
try {
|
| 165 |
const agentConfig = agent.config ? JSON.parse(agent.config) : {}
|
| 166 |
-
|
| 167 |
-
|
|
|
|
| 168 |
const safeSoulPath = resolveWithin(safeWorkspace, 'soul.md')
|
| 169 |
mkdirSync(dirname(safeSoulPath), { recursive: true })
|
| 170 |
writeFileSync(safeSoulPath, newSoulContent || '', 'utf-8')
|
|
|
|
| 4 |
import { join, dirname, isAbsolute, resolve } from 'path';
|
| 5 |
import { config } from '@/lib/config';
|
| 6 |
import { resolveWithin } from '@/lib/paths';
|
| 7 |
+
import { getAgentWorkspaceCandidates, readAgentWorkspaceFile } from '@/lib/agent-workspace';
|
| 8 |
import { requireRole } from '@/lib/auth';
|
| 9 |
import { logger } from '@/lib/logger';
|
| 10 |
|
|
|
|
| 48 |
|
| 49 |
try {
|
| 50 |
const agentConfig = agent.config ? JSON.parse(agent.config) : {}
|
| 51 |
+
const candidates = getAgentWorkspaceCandidates(agentConfig, agent.name)
|
| 52 |
+
const match = readAgentWorkspaceFile(candidates, ['soul.md', 'SOUL.md'])
|
| 53 |
+
if (match.exists) {
|
| 54 |
+
soulContent = match.content
|
| 55 |
+
source = 'workspace'
|
|
|
|
|
|
|
| 56 |
}
|
| 57 |
} catch (err) {
|
| 58 |
logger.warn({ err, agent: agent.name }, 'Failed to read soul.md from workspace')
|
|
|
|
| 162 |
let savedToWorkspace = false
|
| 163 |
try {
|
| 164 |
const agentConfig = agent.config ? JSON.parse(agent.config) : {}
|
| 165 |
+
const candidates = getAgentWorkspaceCandidates(agentConfig, agent.name)
|
| 166 |
+
const safeWorkspace = candidates[0]
|
| 167 |
+
if (safeWorkspace) {
|
| 168 |
const safeSoulPath = resolveWithin(safeWorkspace, 'soul.md')
|
| 169 |
mkdirSync(dirname(safeSoulPath), { recursive: true })
|
| 170 |
writeFileSync(safeSoulPath, newSoulContent || '', 'utf-8')
|