@flame0510/project-aether 1.6.2 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -2
  3. package/agent-templates/atlas/HEARTBEAT.md +1 -1
  4. package/app/agents/ImageDownloadBanner.tsx +171 -37
  5. package/app/api/agents/download-image/route.ts +29 -5
  6. package/app/api/agents/image-status/route.ts +17 -2
  7. package/app/api/assistant/route.ts +21 -5
  8. package/app/api/auth/login/route.ts +2 -2
  9. package/app/api/metrics/route.ts +126 -23
  10. package/app/api/setup/agent-image/route.ts +6 -4
  11. package/app/api/system-health/route.ts +25 -21
  12. package/app/components/DashboardToolbar.tsx +2 -2
  13. package/app/components/LineageGraphPage.tsx +4 -4
  14. package/app/components/SessionDrawer.tsx +8 -8
  15. package/app/components/Sidebar.tsx +10 -0
  16. package/app/components/Skeleton.tsx +4 -1
  17. package/app/components/SystemCockpit.tsx +83 -3
  18. package/app/components/ui/Meter.tsx +34 -0
  19. package/app/components/ui/TimeSeriesChart.tsx +226 -0
  20. package/app/components/ui/index.ts +2 -0
  21. package/app/globals.css +42 -1
  22. package/app/setup/PageClient.tsx +1 -1
  23. package/app/system/PageClient.tsx +263 -0
  24. package/app/system/SystemSkeleton.tsx +115 -0
  25. package/app/system/loading.tsx +13 -0
  26. package/app/system/page.tsx +5 -0
  27. package/bin/postinstall.js +5 -1
  28. package/daemon.js +274 -214
  29. package/docs/ARCHITECTURE.md +65 -34
  30. package/docs/CONTAINER-TERMINAL.md +17 -8
  31. package/docs/DESIGN-SYSTEM.md +22 -12
  32. package/docs/FRONTEND-ARCHITECTURE.md +8 -4
  33. package/docs/REV4A.md +37 -86
  34. package/docs/dev/API-REFERENCE.md +102 -19
  35. package/docs/dev/DATABASE.md +79 -28
  36. package/docs/dev/SESSION-MAINTENANCE-PLAN.md +6 -6
  37. package/docs/rag/DATA-FRESHNESS.md +31 -15
  38. package/docs/rag/GLOSSARY.md +8 -5
  39. package/docs/rag/REV4A-OVERVIEW.md +14 -7
  40. package/docs/rag/WHAT-I-CAN-ANSWER.md +4 -3
  41. package/lib/agent-images.ts +43 -14
  42. package/lib/buildAgentImage.ts +142 -6
  43. package/lib/db-bootstrap.mjs +0 -11
  44. package/lib/metrics-db.ts +48 -0
  45. package/lib/patterns/sessionPresentation.ts +4 -2
  46. package/lib/rev4a-auth.d.ts +1 -0
  47. package/lib/rev4a-auth.js +18 -2
  48. package/next.config.mjs +9 -1
  49. package/package.json +2 -2
  50. package/scripts/backup.sh +48 -54
  51. package/scripts/check-language.mjs +21 -3
  52. package/scripts/restore.sh +77 -59
@@ -1,7 +1,7 @@
1
1
  # Rev4a Architecture — Design & Vision
2
2
 
3
3
  > **Status:** Active — `main` branch
4
- > **Last updated:** 2026-09-22
4
+ > **Last updated:** 2026-09-26
5
5
  > **Goal:** Transform Rev4a from a monitoring dashboard into a central orchestrator for a distributed multi-container agency.
6
6
 
7
7
  ---
@@ -35,7 +35,7 @@
35
35
 
36
36
  ```
37
37
  ┌────────────────────────────────────────────────────────────────┐
38
- │ DOCKER HOST (187.77.156.41, 4 CPU, 15 GB RAM, 193 GB disk) │
38
+ │ DOCKER HOST │
39
39
  │ │
40
40
  │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐ │
41
41
  │ │ rev4a-control │ │ agent-argus │ │ agent-atlas │ │
@@ -72,7 +72,7 @@
72
72
 
73
73
  **Credentials vault:** The `/credentials` page stores third-party service tokens in `credentials.db` and installs them into containers on explicit sync. Provider API keys live separately in `provider-keys.json`, managed from the Gateway UI.
74
74
 
75
- **Container management UI:** The `/containers` page lists all running Docker containers with resource usage and quick links, and opens a web terminal into any of them. Fully functional.
75
+ **Container management UI:** The `/containers` page lists all Docker containers, running or stopped, with status, image, IP and ports, and opens a web terminal into any of them. Fully functional. (Machine CPU, RAM and disk are on `/system`.)
76
76
 
77
77
  **Workspace API:** Lazy-loaded file tree explorer with real-time reads (no caching), supports both host and container workspaces via `docker exec`.
78
78
 
@@ -81,7 +81,7 @@
81
81
  | Feature | Status | Notes |
82
82
  |---|---|---|
83
83
  | Credentials (UI + API) | 🟢 Implemented | SQLite, no encryption yet |
84
- | Container page | 🟢 Functional | Lists all containers, resources, web terminal |
84
+ | Container page | 🟢 Functional | Lists all containers, web terminal |
85
85
  | Agent creation wizard | 🟢 Implemented | One-click create with model/template selection |
86
86
  | Provider proxy | 🟢 Implemented | Agents route through Rev4a provider gateway |
87
87
  | Shared volumes | 🟢 Implemented | Skills + repos mounted on all agents |
@@ -132,6 +132,18 @@ The central container, running the Next.js dashboard + orchestration API.
132
132
  a key), `isModelOffered()` (enforced by the proxy and the assistant) and
133
133
  `catalogueStatus()`. A failed read of `models.config.json` falls back to the last
134
134
  good copy and never deletes overrides.
135
+ - **The model data has three files and three scripts.** `models.config.json` is curated by
136
+ hand (identity, `enabled`, `deprecated`, and an optional `info` block for what no API
137
+ publishes: the vendor's size claim, benchmarks, the docs link, notes).
138
+ `model-pricing.json` and `model-details.json` are **generated** by
139
+ `npm run refresh:pricing` (`scripts/refresh-model-pricing.mjs`): prices from OpenRouter,
140
+ and per model the description, architecture and benchmarks from OpenRouter plus the size,
141
+ weight mix, licence and dates from the Hugging Face card. Two scripts back the curation
142
+ of `info`: `npm run info:suggest` (`scripts/model-info-suggest.mjs`) prints the candidate
143
+ facts from a card, and `npm run info:verify` (`scripts/model-info-verify.mjs`) refuses a
144
+ proposal the card does not support — evidence per field, every benchmark value under the
145
+ column that names this model. `GET /api/models/details` merges the three files for the
146
+ details modal (`docs/dev/GATEWAY.md`).
135
147
  - **Agent images and OpenClaw versions.** `lib/agent-versions.json` lists the supported
136
148
  OpenClaw versions, newest first, with the model `input` list for each; the server reads
137
149
  it through `lib/agent-versions.ts`, the `rev4a` CLI with `require`. The provider sync
@@ -560,19 +572,19 @@ The Rev4a daemon (`daemon.js`) is a standalone Node.js process that bridges the
560
572
 
561
573
  ### Responsibilities
562
574
 
563
- 1. Poll `openclaw sessions --json --all-agents` on a configurable interval
575
+ 1. Poll `openclaw sessions --json --all-agents` every 30 s
564
576
  2. Upsert session rows into `events.db`
565
- 3. Emit `spawn` / `complete` / `error` events into the `events` table
566
- 4. Collect system metrics (CPU, RAM, disk) every poll cycle
567
- 5. Detect anomalies (CPU > 90%, RAM > 90%) and log warnings
568
- 6. Manage DB lifecycle (WAL mode, checkpoint after each cycle)
577
+ 3. Emit `spawn` / `complete` / `fail` / `spawn_timeout` events into the `events` table
578
+ 4. Sample the host machine (CPU, RAM, swap, storage) every 30 s into `metrics.db`, on a
579
+ timer of its own
580
+ 5. Detect anomalies (CPU > 85%, RAM > 90%, a disk > 90%) and record them as events
581
+ 6. Manage DB lifecycle (WAL mode; events.db checkpointed after each poll cycle)
569
582
 
570
- ### Poll Intervals
583
+ ### Poll Interval
571
584
 
572
- | Condition | Interval |
573
- |---|---|
574
- | No active sessions | 30 s |
575
- | ≥ 1 session with status `working` | 15 s |
585
+ A fixed 30 s timer, whether or not sessions are working. When the OpenClaw CLI is not on
586
+ the host, the session poll logs it once and stays idle; the machine metrics keep being
587
+ sampled, since they have their own timer.
576
588
 
577
589
  ### Cost Estimation
578
590
 
@@ -614,28 +626,45 @@ The daemon uses `INSERT … ON CONFLICT DO UPDATE` with these rules:
614
626
 
615
627
  ### System Metrics
616
628
 
617
- Every poll cycle the daemon records a `system_metrics` row. Data sources:
618
-
619
- - **CPU**: cgroup v2 usage delta (`/sys/fs/cgroup/cpu.stat`) when available, falls back to `/proc/stat` host ticks
620
- - **RAM**: `/proc/meminfo`
621
- - **Disk**: `df -BG /data`
622
- - **Load**: `os.loadavg()[0]`
623
-
624
- Metrics older than 24 h are pruned automatically each cycle.
629
+ Every 30 s (`METRICS_INTERVAL_MS`) the daemon samples the machine it runs on and writes a
630
+ `system_metrics` row plus one `system_disks` row per filesystem into `metrics.db` — its
631
+ own database next to `events.db` (schema in `docs/dev/DATABASE.md`). Machine-wide sources:
632
+
633
+ - **CPU**: busy share of all cores since the previous sample, from host ticks
634
+ (`os.cpus()`, i.e. `/proc/stat` on Linux) — the whole machine, not Rev4a's cgroup
635
+ - **RAM / swap**: `/proc/meminfo` on Linux, used = `MemTotal − MemAvailable` (what `free`
636
+ shows); on macOS (development) active + wired + compressed pages from `vm_stat` and
637
+ `vm.swapusage`; elsewhere `os.totalmem()` / `os.freemem()` and no swap
638
+ - **Storage**: `fs.statfsSync()` on `/`, Docker's data root (`docker info`, asked once and
639
+ retried every 10 min while Docker is down) and Rev4a's data directory, each device once;
640
+ a path that cannot be read (Docker Desktop's root inside its VM) is skipped
641
+ - **Load**: `os.loadavg()` 1 / 5 / 15 min
642
+
643
+ The first sample comes about 5 s after start (the CPU figure needs a delta from the baseline
644
+ taken at load), then one every 30 s. Samples older than 30 days are pruned at every sample.
645
+ `metrics.db` is opened in a try: if it cannot be (corrupt, unwritable), sampling is off —
646
+ `[METRICS] disabled` in the log — and the session poll keeps running. The API side reads
647
+ it through `lib/metrics-db.ts` (read-only, `null` when there is nothing to read);
648
+ `GET /api/metrics` serves the System page (`/system`) and the dashboard's Machine card.
625
649
 
626
650
  ### Anomaly Detection
627
651
 
628
- The daemon compares the last two metric samples. If a threshold is exceeded and the cooldown (5 min) has passed, it logs a `WARN` line:
652
+ After each sample the daemon inserts a `system_anomaly` event into `events.db`
653
+ (`{ metric, values, threshold, message }`) and logs `[ANOMALY] <message>` when:
629
654
 
630
- | Metric | Threshold |
631
- |---|---|
632
- | CPU | > 90% |
633
- | RAM | > 90% |
655
+ | Metric | Condition | Repeats |
656
+ |---|---|---|
657
+ | CPU | > 85% on two consecutive samples | at most once per 5 min |
658
+ | RAM | > 90% on two consecutive samples | at most once per 5 min |
659
+ | Disk (each filesystem) | ≥ 90% | once when it reaches it, no cooldown — again only after it drops back under, or after the daemon restarts |
660
+
661
+ No feature consumes `system_anomaly` events yet; they only pass through the generic
662
+ event feeds.
634
663
 
635
664
  ### DB Safety
636
665
 
637
666
  - WAL mode + `PRAGMA synchronous = NORMAL` for concurrent read safety
638
- - `PRAGMA wal_checkpoint(PASSIVE)` runs after each poll cycle
667
+ - `PRAGMA wal_checkpoint(PASSIVE)` runs after each poll cycle, `FULL` every 10 cycles
639
668
  - On `uncaughtException` the daemon logs but does NOT exit — relies on systemd Restart=always for recovery
640
669
 
641
670
  ### Migrations
@@ -722,17 +751,19 @@ ALTER TABLE sessions ADD COLUMN ended_at INTEGER;
722
751
 
723
752
  Rev4a provides a real, interactive terminal for any agent container via
724
753
  a WebSocket-connected PTY. The implementation is documented in detail in
725
- [docs/container-terminal.md](container-terminal.md).
754
+ [docs/CONTAINER-TERMINAL.md](CONTAINER-TERMINAL.md).
726
755
 
727
756
  ### Two-process architecture
728
757
 
729
758
  | Process | Port | Role |
730
759
  |---|---|---|
731
- | `rev4a-next` (Next.js) | 3740 | Serves the terminal page, auth, API |
732
- | `rev4a-terminal-ws` (standalone) | 3741 | WebSocket PTY server via `node-pty` |
760
+ | Next.js (`next start`) | 3740 | Serves the terminal page, auth, API |
761
+ | `terminal-ws-server.js` | 3741 (127.0.0.1) | WebSocket PTY server via `node-pty` |
733
762
 
734
- The terminal server runs as a separate systemd service (rev4a-terminal-ws) to avoid event-loop
735
- contention with Next.js during high-throughput I/O.
763
+ Both are started by `rev4a serve`; the terminal server is a process of its own to avoid
764
+ event-loop contention with Next.js during high-throughput I/O. It has no authentication
765
+ of its own and is reachable only through a reverse proxy route (see
766
+ [CONTAINER-TERMINAL.md](CONTAINER-TERMINAL.md#routing)).
736
767
 
737
768
  ### Why custom DOM over xterm.js
738
769
 
@@ -742,7 +773,7 @@ CSS conflicts. The current implementation uses a plain `<div>` with native
742
773
  browser scrolling and a hidden `<textarea>` for input — stable under any
743
774
  output volume.
744
775
 
745
- See [container-terminal.md](container-terminal.md#terminal-client-browser)
776
+ See [CONTAINER-TERMINAL.md](CONTAINER-TERMINAL.md#terminal-client-browser)
746
777
  for the full rationale.
747
778
 
748
779
  ## 12. Technology Stack
@@ -15,7 +15,7 @@ via `node-pty` and streaming I/O over a dedicated WebSocket server.
15
15
  Browser (xterm-compatible DOM terminal)
16
16
  │ WebSocket wss://rev4a/containers/terminal/{id}
17
17
  ▼
18
- terminal-ws-server.js (port 3741, proxied by Traefik)
18
+ terminal-ws-server.js (127.0.0.1:3741, reached through a reverse proxy — see Routing)
19
19
  │ node-pty
20
20
  ▼
21
21
  docker exec -it {containerId} env TERM=xterm-256color bash
@@ -30,20 +30,30 @@ Container Shell (bash, interactive, full PTY)
30
30
 
31
31
  | Process | Port | Role |
32
32
  |------------------|-------|------|
33
- | `rev4a-next` | 3740 | Next.js app (pages, API routes, auth) |
34
- | `rev4a-terminal-ws` | 3741 | Standalone WebSocket server for terminal sessions |
33
+ | Next.js (`next start`) | 3740 | Next.js app (pages, API routes, auth) |
34
+ | `terminal-ws-server.js` | 3741 | Standalone WebSocket server for terminal sessions, bound to 127.0.0.1 |
35
35
 
36
- The terminal server is a separate systemd service (rev4a-terminal-ws). It was isolated from
36
+ Both are child processes of `rev4a serve` (with `daemon.js`), under the single
37
+ `rev4a.service` unit in production. The terminal server is its own process, isolated from
37
38
  the Next.js app to avoid event-loop contention during high-throughput I/O (fast
38
39
  `seq`, `cat` on large files, interactive shell sessions).
39
40
 
40
- ### Traefik routing
41
+ ### Routing
41
42
 
42
43
  ```
43
44
  /containers/terminal/{id} → Next.js (page serving TerminalClient)
44
- /api/terminal-ws?id={containerId} → WebSocket proxy → ws://127.0.0.1:3741
45
+ /api/terminal-ws?id={containerId} → reverse proxy → ws://127.0.0.1:3741
45
46
  ```
46
47
 
48
+ The browser opens the socket on the dashboard's own host (`/api/terminal-ws`). Next.js
49
+ has no route for that path, so the terminal works only when a reverse proxy in front of
50
+ Rev4a forwards it to port 3741; without one the socket gets a 404.
51
+
52
+ **No authentication of its own.** `terminal-ws-server.js` checks neither the session
53
+ cookie nor the token: whoever reaches it gets a shell in the container named by `id`.
54
+ It listens on 127.0.0.1 only, so the exposure is exactly what the proxy route above
55
+ opens. An open issue.
56
+
47
57
  ### Data flow
48
58
 
49
59
  1. User opens `/containers/terminal/openclaw-atlas`
@@ -257,8 +267,7 @@ app/containers/terminal/
257
267
  ├── [id]/
258
268
  │ ├── page.tsx # Next.js page (auth-protected)
259
269
  │ └── TerminalClient.tsx # Client-side terminal component
260
- terminal-ws-server.js # WebSocket PTY server (rev4a-terminal-ws.service)
261
- rev4a.service # Systemd target for all three services
270
+ terminal-ws-server.js # WebSocket PTY server, started by `rev4a serve`
262
271
  ```
263
272
 
264
273
  ## Known Limitations
@@ -2,7 +2,7 @@
2
2
 
3
3
  Source of truth: `app/design-system.ts` + CSS tokens in `app/globals.css`.
4
4
 
5
- > **Last updated:** 2026-07-03
5
+ > **Last updated:** 2026-09-26
6
6
 
7
7
  ---
8
8
 
@@ -44,19 +44,22 @@ Rev4a uses a **square/sharp** visual language:
44
44
  Defined in `app/globals.css` as CSS custom properties:
45
45
 
46
46
  ```css
47
- --bg: #0a0a0a /* Page background */
48
- --bg2: #111 /* Slightly lighter surface */
49
- --border: #222 /* Default border */
50
- --text: #e0e0e0 /* Primary text */
51
- --text-dim: #888 /* Muted/secondary text */
52
- --violet: #a78bfa /* Brand accent */
53
- --violet-bg: #1a1030 /* Violet-tinted hover/active background */
54
- --green: #22c55e /* Success */
55
- --red: #ef4444 /* Error */
56
- --yellow: #eab308 /* Warning */
57
- --blue: #60a5fa /* Info / links */
47
+ --bg: var(--color-bg-950) /* #0A0A0B Page background */
48
+ --bg2: var(--color-bg-900) /* #111114 Slightly lighter surface */
49
+ --border: var(--color-border-700) /* #222228 Default border */
50
+ --text: var(--color-text-100) /* #E8E8E8 Primary text */
51
+ --text-dim: var(--color-text-400) /* #888 Muted/secondary text */
52
+ --violet: var(--color-accent-500) /* #925BFC Brand accent */
53
+ --violet-bg: rgba(146, 91, 252, 0.08) /* Violet-tinted hover/active background */
54
+ --green: var(--color-success-500) /* #22c55e Success */
55
+ --red: var(--color-danger-500) /* #ef4444 Error */
56
+ --yellow: var(--color-warning-500) /* #f59e0b Warning */
57
+ --blue: var(--color-blue-400) /* #60a5fa Info / links */
58
58
  ```
59
59
 
60
+ The semantic tokens above point at the palette tokens (`--color-*`) defined at the top of
61
+ `app/globals.css`; the hex values are the palette's, quoted for reference.
62
+
60
63
  ### Typography
61
64
 
62
65
  - Monospace by default: `var(--font-mono-stack)`
@@ -66,6 +69,12 @@ Defined in `app/globals.css` as CSS custom properties:
66
69
 
67
70
  ## Component design rules
68
71
 
72
+ A shared component grows by **variant, never by copy**. `Metric` is the example: `size="sm"`
73
+ is the compact form for a grid inside a dialog (smaller value, tighter spacing), and it lives
74
+ on the primitive so the modal and the dashboard cannot drift apart. When a new page needs a
75
+ pattern that does not exist yet, add it here and to `docs/FRONTEND-ARCHITECTURE.md`'s catalog,
76
+ then use it — a one-off copy of markup is the thing this section exists to prevent.
77
+
69
78
  1. **Border radius: 0 everywhere.** `border-radius: 0` or sharp corners. Exceptions: `Pill`, avatar circles, status dots.
70
79
  2. **No shadows.** Flat design. Use `border: 1px solid var(--border)` to define surfaces.
71
80
  3. **Violet is the only accent.** `var(--violet)` for active states, primary actions, headings. `var(--violet-bg)` for hover/active backgrounds.
@@ -73,6 +82,7 @@ Defined in `app/globals.css` as CSS custom properties:
73
82
  5. **CSS vars over hardcoded colors.** Never inline `#22c55e`, `#ef4444`, `#888` — use the CSS token.
74
83
  6. **Loading feedback.** Use `loading` prop on `Button` (shows inline spinner). Do not render separate loader elements.
75
84
  7. **Minimal animation.** Transitions on hover/active (0.1s–0.12s), spin on loading spinner. No bounce, shake, or decorative animations.
85
+ 8. **Data display: violet for the data, status colours for levels.** A chart series is `var(--violet)` (2px line, 10% wash), grid and axis text are `var(--border)` / `var(--text-dim)`, never the series colour. A `Meter` fill is violet when fine and `var(--yellow)` / `var(--red)` at its warning / critical level, with the level also written out — status never rests on colour alone. Focus on a chart is a 1px `outline` in `var(--violet-dim)`, not a shadow. See `TimeSeriesChart` and `Meter` in `docs/FRONTEND-ARCHITECTURE.md`.
76
86
 
77
87
  ### React hook
78
88
 
@@ -1,6 +1,6 @@
1
1
  # Rev4a Frontend Architecture
2
2
 
3
- > **Last updated:** 2026-09-22
3
+ > **Last updated:** 2026-09-26
4
4
 
5
5
  ## Layering
6
6
 
@@ -38,6 +38,8 @@ All shared UI primitives live in `app/components/ui/` and are exported from `app
38
38
  | `ModalityIcons` | `ModalityIcons.tsx` | Capability badges parsed from a model's `modality` string (`"text+image->text"`). Input types render as icons; a non-text **output** is called out separately, since reading an image and generating one are different capabilities. Pass `dynamic` for router models, which advertise the union of everything they might route to and so show "varies" instead. |
39
39
  | `Metric` | `Metric.tsx` | Metric card with title, value, subtitle, tone, and a `size` (`md` default, `sm` for a grid inside a dialog: smaller value, tighter spacing). |
40
40
  | `StatusCard` | `StatusCard.tsx` | Health/status report card. |
41
+ | `Meter` / `meterLevel` | `Meter.tsx` | Horizontal 0-100 % fill whose colour carries the level (`ok` accent, `warning` yellow, `critical` red); the level is also written out beside it, never colour alone. `meterLevel(percent, warning, critical)` picks the level. ARIA `role="meter"`. |
42
+ | `TimeSeriesChart` | `TimeSeriesChart.tsx` | One series over time in plain SVG measured to its container (text never scaled): a 2px accent line for the value, a 10 % wash up to the bucket's peak, a hairline grid at 0 / 50 / 100 %, the latest value labelled at the line's end. Points further apart than 1.5 buckets are not joined (a gap means no samples). Hover or keyboard focus (← → Esc) shows a crosshair snapped to the nearest point with value and peak; a **Table** disclosure holds the same data. `stale` dims the previous render while a new range loads. Used by the System page. |
41
43
  | `Surface` | `Surface.tsx` | Shared panel/card surface, variant prop. |
42
44
  | `Page` / `PageHeader` | `Page.tsx` | Full-page layout shell. |
43
45
  | `Icons` | `Icons.tsx` | SVG icons (`EyeIcon`, `EyeOffIcon`), 16/20px shared. |
@@ -45,7 +47,9 @@ All shared UI primitives live in `app/components/ui/` and are exported from `app
45
47
  | `UpdateSection` | `app/agents/UpdateSection.tsx` | OPENCLAW VERSION section of the agent detail panel: the version the agent runs, **Update to <version>** when a newer supported version is downloaded (confirm modal, disabled while the agent is stopped), the running update's steps with backup progress, the outcome, and **Roll back to <version>** after an update. Polls `/api/agents/[id]/update` every 2 s while an update or rollback runs; one action at a time (keyed busy state). |
46
48
  | `BackupSection` | inline in `app/agents/PageClient.tsx` | BACKUP section of the agent detail panel, on the cold backup and the restore. **Backup Now** starts `POST /api/agents/[id]/cold-backup`; while the job runs a banner shows the file and its live percent with **Cancel** (`DELETE /cold-backup`). **Restore** (after a confirm) starts `POST /restore` and a banner shows `Restoring <file>…` (no percent: the extract is a single `tar xzf`, and there is no Cancel). Both sections poll their `GET` every 2 s while running, and on mount pick up a job that is already running — a backup lives in a Docker helper, a restore in `agent_restores`, so reloading the page or navigating away never loses them nor allows a second one (the server answers 409 anyway). Delete per row; all actions disabled while one runs; keyed busy state `{ kind, file }` so only the row in action shows the spinner. On the agent list, an activity Badge (fed by `/api/agents/activity-summary`, polled at 2 s only while something runs, otherwise riding the 15 s list poll) reads `BACKUP nn%`, `RESTORING`, `RECREATING`, `EDITING` or `UPDATING`. |
47
49
  | `RecreateSection` | inline in `app/agents/PageClient.tsx` | RECREATE section of the agent detail panel. **Recreate Container** starts `POST /api/agents/[id]/recreate` (202) after a confirm; a banner then shows the phase — *Backing up … nn%* while the cold backup runs, *Recreating container…* while the container is rebuilt and the gateway starts. The section polls `GET /recreate` every 2 s, and on mount picks up a recreate that is already running, so a reload or navigation never loses it; it refetches the agent once the job reports `done`. |
48
- | `ImageDownloadBanner` | `app/agents/ImageDownloadBanner.tsx` | Agent image banner on the Agents page. Polls `/api/agents/image-status` every 2 s; offers **Download Image** when no supported version is downloaded, **Download <version>** when the registry publishes a newer one, and shows the download in progress and its completion. Downloading changes no agent. |
50
+ | System page | `app/system/PageClient.tsx` | `/system`: CPU, memory and storage of the host, from `GET /api/metrics`. Three cards (CPU with cores and load; memory with available and swap; one `Meter` per filesystem with its roles) — thresholds 85/95 % for CPU and memory, 80/90 % for storage — then a range switch (`Tabs`: 1h · 24h · 7d · 30d) scoping the charts below it: CPU and memory average with peak, one chart per filesystem. Polls every 30 s with an `AbortController` ref (a range change aborts the previous fetch). Times in the configured Rev4a timezone (`useRev4aTimezone`). Before the first answer the cards and charts are skeletons shaped like them (`app/system/SystemSkeleton.tsx`, also the route's `loading.tsx`), so nothing jumps; the hostname line is cut with an ellipsis and never widens the page. Says *No samples yet* before the daemon's first sample and *Not collecting* when the latest sample is older than three intervals. Two columns of charts from lg (992px) up, one below. |
51
+ | Machine card | `app/components/SystemCockpit.tsx` | On the dashboard, next to Active sessions: CPU, RAM and the fullest disk now, each figure coloured only when past its threshold (same thresholds as the System page), the issues named in words, and a link to `/system`. It says *No samples yet* before the first sample, *Not collecting* when the newest is stale, and *Metrics unavailable* when a refresh fails (keeping the last figures). A `Surface`, not a `Metric`: a danger `Metric` paints its whole value red. |
52
+ | `ImageDownloadBanner` | `app/agents/ImageDownloadBanner.tsx` | Agent image banner on the Agents page. Polls `/api/agents/image-status` every 2 s; offers **Download Image** when no supported version is downloaded, **Download <version>** when the registry publishes a newer one, and while a download runs shows the percent of layers finished and the latest line of docker's output (the bar is indeterminate until a percent can be computed). **Cancel** (`DELETE /api/agents/download-image`, own loading state) appears once `image-status` reports the download running; before that the button reads *Starting…* and is disabled. Every outcome — ready and cancelled for 3 s, a failure until the next action — comes from the server's `lastResult`, so one that ended while the page was closed or reloading is still reported if it is less than 30 s old (aged with `serverTime`). Each outcome is announced once per page load, although the Agents page mounts the banner in three places (mobile list, mobile detail, desktop). Downloading changes no agent. |
49
53
  | `VersionBanner` | `app/components/VersionBanner.tsx` | "Update available" banner for Rev4a itself, when `GET /api/update-check?check=1` reports a newer published version (dismissable per version, remembered in `localStorage`). **Update now** starts `POST /api/update-check`; because that update restarts the server, the banner cannot be told the outcome by the response: it records what it asked for in `sessionStorage`, polls `/api/update-check` until the installed version moves (two minutes at most) and reloads, then on the next mount either confirms "Updated to vX" or reports that the update did not complete and points at `update.log` (the update's own output). It never reloads blindly onto the same version. |
50
54
  | `BrowserAccessSection` / `OpenControlUiButton` | `app/agents/BrowserAccessSection.tsx` | Browser access to one agent's Control UI, in its detail panel: requests waiting for approval (Approve / Reject) and approved browsers (Rename / Revoke), refreshed every 5 s while mounted. A successful approve, reject, rename or revoke updates the list at once, since the refresh behind it runs the OpenClaw CLI and takes seconds; a read started before the mutation is discarded. On agents that require approval, "Invite link" fetches `/api/agents/[id]/invite-link` and shows the link in a read-only field with Copy, which uses the Clipboard API in a secure context and the field's selection over plain HTTP, plus a warning when the link uses localhost. `OpenControlUiButton` opens `/api/agents/[id]/open-control-ui` in a new tab inside the click; that route redirects to a one-time link that pairs the browser with no approval, or to the plain token link when none can be issued. Used on the agent cards and in the panel. |
51
55
  | `ChannelManager` / `ChannelSection` | `app/agents/ChannelManager.tsx`, `ChannelSection` inline in `app/agents/PageClient.tsx` | Telegram, in the agent detail panel (`ChannelSection` is the card that opens the modal; the modal title is the agent's display name). Reads `GET /channels`; lists pending pairing requests with **Approve** (`POST /channels/pairing`) and approved senders with **Revoke** after a confirm (`DELETE /channels/pairing?senderId=`). Pending comes from `openclaw pairing list`, approved from OpenClaw's pairing store (`lib/channelManager.ts`). When that store cannot be read the panel shows the reason instead of "No approved senders" (`PairingState.error`), so an empty list is never a guess. Polls pairings every 5 s while open; one keyed busy state per action. |
@@ -58,7 +62,7 @@ All shared UI primitives live in `app/components/ui/` and are exported from `app
58
62
  | Wizard icons | `app/wizard/icons.tsx` | Shared SVG icons (flyweight pattern): `ArrowRightIcon`, `CheckIcon`, `CheckCircleIcon`, `DockerIcon`, `GatewayIcon`, `AgentIcon`, `TemplateIcon`, `LinkIcon`, `ConfigIcon`, `InformationIcon`. |
59
63
  | `tokens` | `tokens.ts` | TypeScript types for `Tone` and related token values. |
60
64
  | `Badge` | `Badge.tsx` | Inline status tag with tone variants (success, danger, warning, neutral). Used for channel chips, pairing labels, error/success messages. |
61
- | `Skeleton` + shape helpers | `app/components/Skeleton.tsx` | Shimmer placeholders. `Skeleton` is the primitive; the rest mirror one specific layout each: `TreeSkeleton`, `CodeSkeleton`, `CronJobsSkeleton`, `CronRunsSkeleton`, `CredentialCardsSkeleton`, `AgentSyncRowsSkeleton`, `SkeletonLines`, `SkeletonMetric`, `CardRowSkeleton`. A shape helper must match the real markup it stands in for — same row structure, same paddings, same element count where the count is known — so nothing reflows when data replaces it. |
65
+ | `Skeleton` + shape helpers | `app/components/Skeleton.tsx` | Shimmer placeholders. `Skeleton` is the primitive; the rest mirror one specific layout each: `TreeSkeleton`, `CodeSkeleton`, `CronJobsSkeleton`, `CronRunsSkeleton`, `CredentialCardsSkeleton`, `AgentSyncRowsSkeleton`, `SkeletonLines`, `SkeletonMetric`, `CardRowSkeleton`. A shape helper must match the real markup it stands in for — same row structure, same paddings, same element count where the count is known — so nothing reflows when data replaces it. Inside text (a `<p>`, a `<span>`) pass `as="span"`: a `<div>` there is invalid HTML, and the parser splits the paragraph in the server-rendered page. The System page keeps its own shapes in `app/system/SystemSkeleton.tsx`, each shimmer inside the class of the text it replaces so the line boxes are the real ones — measured to 0 px of movement on desktop and mobile. |
62
66
 
63
67
  ### Rules
64
68
 
@@ -153,7 +157,7 @@ For interactive pages that can change view/file/tab quickly:
153
157
  For every new/changed page:
154
158
 
155
159
  1. No new one-off card styles unless justified.
156
- 2. Use `app/components/ui/*` primitives first — `Button`, `Input`, `Select`, `Modal`, `Toast`, `Surface`, `Metric`, `StatusCard`, `Pill`, `Page`, `Tabs`, `ItemList`, `FilterBar`, `PropertyList`, `TemplateOption`, `LoadingSpinner`.
160
+ 2. Use `app/components/ui/*` primitives first — `Button`, `Input`, `Select`, `Modal`, `Toast`, `Surface`, `Metric`, `StatusCard`, `Meter`, `TimeSeriesChart`, `Pill`, `Page`, `Tabs`, `ItemList`, `FilterBar`, `PropertyList`, `TemplateOption`, `LoadingSpinner`.
157
161
  3. Async data must render skeleton, not fake zero values.
158
162
  4. Business logic stays in `lib`/API routes; UI consumes typed payloads.
159
163
  5. If a pattern is introduced, name it and keep it in `lib/patterns` or `app/components/ui`.
package/docs/REV4A.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Rev4a — VPS Dashboard
2
2
 
3
- > **Last updated:** 2026-09-15
3
+ > **Last updated:** 2026-09-26
4
4
 
5
5
  A Next.js 16 dashboard for monitoring and managing the OpenClaw ecosystem.
6
6
 
@@ -9,50 +9,34 @@ A Next.js 16 dashboard for monitoring and managing the OpenClaw ecosystem.
9
9
  **Process manager:** `rev4a serve` (single command). Production: systemd service (`rev4a.service`).
10
10
  **Repository:** `github.com/Flame0510/rev4a.git` (branch: `main`)
11
11
 
12
- ---
13
-
14
- ## Overview
15
-
16
- | Field | Value |
17
- |---|---|
18
- | VPS IP | `187.77.156.41` |
19
- | OS | Ubuntu 24.04.4 LTS |
20
- | Resources | 4 CPU, 15 GB RAM, 193 GB disk |
21
-
22
-
23
12
  ---
24
13
 
25
14
  ## Architecture
26
15
 
27
16
  ```
28
- Internet
17
+ Browser (directly, or through a reverse proxy of your choice)
29
18
  │
30
- ┌────┴────┐
31
- │ Traefik │ (Docker container, host network)
32
- │ :443 │
33
- └────┬────┘
19
+ ┌──────────┴──────────────────┐
20
+ │ rev4a serve │
21
+ │ (systemd: rev4a.service) │
22
+ │ Next.js 16 :3740 │
23
+ │ daemon.js │
24
+ │ terminal WS :3741 │
25
+ └──────────┬──────────────────┘
34
26
  │
35
- ┌──────────┴──────────┐
36
- │ rev4a │
37
- │ systemd (rev4a.service) │
38
- │ :3740 │
39
- │ Next.js 16 │
40
- └─────────────────────┘
27
+ docker exec / Docker API
41
28
  │
42
29
  ┌─────────────┴─────────────┐
43
- │ │
44
- docker exec / API docker exec / API
45
- │ │
46
- ┌──────┴──────┐ ┌────────┴────────┐
47
- │ openclaw-core│ │ openclaw-atlas │
48
- │ :3700-3708 │ │ :3731 → 3000 │
49
- │ AGENT_ID=core│ │ AGENT_ID=atlas │
50
- └──────────────┘ └─────────────────┘
30
+ ┌──────┴──────┐ ┌──────┴──────┐
31
+ │ agent │ … │ agent │
32
+ │ container │ │ container │
33
+ │ (OpenClaw) │ │ (OpenClaw) │
34
+ └─────────────┘ └─────────────┘
51
35
  ```
52
36
 
53
37
  ### Environment
54
38
 
55
- Rev4a reads the following environment variables. Set them in `/config` (UI) or directly in `.env`. See [.env.example](../.env.example) for the canonical, always-current list.
39
+ Rev4a reads the following environment variables. Set them in `/config` (UI) or directly in `.env`, except the three read before `.env` is loaded — `REV4A_DATA_DIR`, `REV4A_DOCKER_WAIT_SECONDS` (both from the process environment: a shell export or a systemd `Environment=` line) and `REV4A_SKIP_POSTINSTALL_BUILD` (read by `npm install`). See [.env.example](../.env.example) for the canonical, always-current list.
56
40
 
57
41
  | Variable | Required | Description |
58
42
  |---|---|---|
@@ -67,8 +51,11 @@ Rev4a reads the following environment variables. Set them in `/config` (UI) or d
67
51
  | `REV4A_WORKSPACE_ROOT` | No | Filesystem path for the local file explorer |
68
52
  | `REV4A_ROOT` | No | Installation root, used by `rev4a update` to detect a git checkout |
69
53
  | `REV4A_TIMEZONE` | No | IANA timezone used for scheduling and timestamps |
70
- | `REV4A_FORCE_INSECURE_COOKIE` | No | Allow a non-Secure session cookie — plain-HTTP deployments only |
54
+ | `REV4A_FORCE_INSECURE_COOKIE` | No | `1` allows a non-Secure session cookie — plain-HTTP deployments only (any other value, `true` included, does nothing) |
71
55
  | `REV4A_AGENT_IMAGE_REGISTRY` | No | Registry repository agent images are pulled from (default: `ghcr.io/flame0510/rev4a/openclaw-agent-base`); a `localhost` registry is reached over plain HTTP |
56
+ | `REV4A_DOCKER_WAIT_SECONDS` | No | How long `rev4a serve` waits for the Docker daemon at start-up (default `90`, capped at 600) |
57
+ | `REV4A_DEV_ORIGINS` | No | Extra comma-separated hostnames or IPs allowed to reach `next dev`; no effect on a production build |
58
+ | `REV4A_SKIP_POSTINSTALL_BUILD` | No | `1` in CI: `npm ci` then skips the post-install `next build` (the workflows build as a separate step). Any non-empty value skips it, `0` included — leave it unset to build |
72
59
 
73
60
  See [Alerts](#alerts-telegram) below for the Telegram alert variables.
74
61
 
@@ -91,10 +78,12 @@ to any specific reverse proxy.
91
78
 
92
79
  When environment variables are saved via the Config page (Save & Restart):
93
80
 
94
- 1. Values are written to `.env` in the project root
95
- 2. Secrets are loaded via `EnvironmentFile=` pointing to `.env` (chmod 600). Only non-secret vars (`NODE_ENV`, `PATH`, `REV4A_DB`) are in the unit file. Other vars (passwords, non-Rev4a keys) stay in `.env` for the application to read at runtime.
96
- 3. `systemctl daemon-reload` is run
97
- 4. The server restarts with the new environment
81
+ 1. Values are merged into the data directory's `.env` (`~/.config/rev4a/.env`; the
82
+ `.env` in the installation root is a link to it). An empty value removes the key.
83
+ 2. `sudo systemctl daemon-reload` is attempted; it is harmless where there is no systemd.
84
+ 3. **Save & Restart** runs `sudo systemctl restart rev4a.service`: the restart only works
85
+ when Rev4a runs as that systemd service. Started any other way, restart `rev4a serve`
86
+ yourself — `serve` reads `.env` at start-up, so nothing changes until then.
98
87
 
99
88
  ## Components
100
89
 
@@ -108,13 +97,15 @@ When environment variables are saved via the Config page (Save & Restart):
108
97
 
109
98
  | Route | Description |
110
99
  |---|---|
111
- | `/` | System overview — containers, resources, quick links |
100
+ | `/` | System overview — health, sessions, the machine's CPU / RAM / disk (links to `/system`), cost, live feed |
112
101
  | `/agents` | Running agents (Docker containers with `AGENT_ID`), gateway token management, agent creation wizard, channel manager (Telegram pairing) |
113
102
  | `/containers` | All Docker containers on the host |
103
+ | `/system` | The host machine: CPU, memory and swap, storage per filesystem — current values and 1h / 24h / 7d / 30d history (from `metrics.db`, sampled by the daemon every 30 s) |
114
104
  | `/wizard` | First-run setup wizard (Welcome → Providers → Agent → Ready) |
115
105
  | `/workspace` | File explorer with tree view + editor — VPS host or container workspaces |
116
106
  | `/lineage` | Agent lineage / orchestration tree |
117
107
  | `/memory` | Agent memory browser |
108
+ | `/gateway` (model rows) | Each model has a **Details** (`⋯`) action opening a modal with what is known about it: description, price and the provider it comes from, architecture and parameters (mixture of experts, layers, hidden size, attention heads, vision encoder, precision, weights on disk), reasoning modes, capabilities, benchmarks with their sources, licence and popularity. The facts come from the model's card on Hugging Face and — for the vendor's own size claim and benchmarks — from hand-curated entries that always cite a source and a date; nothing is estimated. |
118
109
  | `/crons` | Scheduled cron jobs — auto-discovered from the host gateway and every running agent container (Docker containers with `AGENT_ID` label), on/off toggle, run history per job |
119
110
  | `/credentials` | Third-party tool credentials (GitHub, Trello, Vercel, Supabase, Notion) — SQLite vault, synced into agent containers as CLI config or, for the REST-only providers, as a config file the agent reads |
120
111
  | `/setup` | First-run password setup — sets `REV4A_PASSWORD` and the JWT secret before the dashboard is reachable |
@@ -159,7 +150,7 @@ REV4A_TELEGRAM_BOT_TOKEN= # Telegram bot token
159
150
  REV4A_TELEGRAM_CHAT_ID= # Telegram chat to notify
160
151
  REV4A_ALERT_COOLDOWN_MS=600000 # Minimum time between repeat alerts for the same condition
161
152
  REV4A_ALERT_STALE_SECONDS=120 # How long without a DB write before the system is considered stale
162
- REV4A_ALERT_SMOKE=false # Set true to send a one-off test alert on startup
153
+ REV4A_ALERT_SMOKE=false # true enables GET /api/alerts/smoke (sends a test alert) in production
163
154
  ```
164
155
 
165
156
  **Behavior:**
@@ -220,14 +211,16 @@ echo 'vm.swappiness=10' > /etc/sysctl.d/99-rev4a-swap.conf
220
211
 
221
212
  ### Systemd environment override
222
213
 
223
- File: `/etc/systemd/system/rev4a-next.service` (EnvironmentFile)
214
+ File: `/etc/systemd/system/rev4a.service.d/override.conf` — a drop-in for the single
215
+ `rev4a.service` unit (`systemctl edit rev4a`). It is for the variables `.env` does not
216
+ carry: `rev4a serve` reads `~/.config/rev4a/.env` itself, and a key present there wins
217
+ over the same key set here — so secrets such as `REV4A_PASSWORD` or `REV4A_TOKEN`
218
+ belong in `.env` (or `/config`), not in the drop-in. It is also the only place for the
219
+ variables read before `.env` is loaded (`REV4A_DATA_DIR`, `REV4A_DOCKER_WAIT_SECONDS`).
224
220
 
225
221
  ```ini
226
222
  [Service]
227
- Environment=REV4A_PASSWORD=***
228
- Environment=REV4A_TOKEN=***
229
- Environment=REV4A_JWT_SECRET=***
230
- Environment=REV4A_DB=/path/to/rev4a/data/events.db
223
+ Environment=REV4A_DOCKER_WAIT_SECONDS=180
231
224
  Environment=NODE_ENV=production
232
225
  Environment=NEXT_TELEMETRY_DISABLED=1
233
226
  ```
@@ -484,48 +477,6 @@ Only models whose provider has a key in `data/provider-keys.json` are synced.
484
477
 
485
478
  ---
486
479
 
487
- ## Containers
488
-
489
- ### openclaw-atlas
490
-
491
- | Property | Value |
492
- |---|---|
493
- | Image | `openclaw-agent-base:<version>` (Node 24-bookworm-slim + OpenClaw) |
494
- | Port | `0.0.0.0:3731 → 3000` |
495
- | IP | `172.19.0.3` |
496
- | AGENT_ID | `atlas` |
497
- | Config mount | `/docker/atlas-data/openclaw-fixed.json → /root/.openclaw/openclaw.json` |
498
- | Auth mode | `token` |
499
-
500
- **Control UI:** `http://<vps-ip>:3731` — enter the gateway token to log in.
501
- Rev4a-generated direct links should use `#token=<gateway-token>` for automatic Control UI sign-in.
502
-
503
- ### openclaw-core
504
-
505
- | Property | Value |
506
- |---|---|
507
- | Image | `ghcr.io/hostinger/hvps-openclaw:latest` |
508
- | Ports | `:3711-3719` (mapped to container `:3700-3708`) |
509
- | AGENT_ID | `core` |
510
-
511
- ### openclaw-giacomo
512
-
513
- | Property | Value |
514
- |---|---|
515
- | Image | `ghcr.io/hostinger/hvps-openclaw:latest` |
516
- | Port | `:3730` |
517
- | AGENT_ID | `giacomo` |
518
-
519
- ### Other containers
520
-
521
- | Name | Image | Notes |
522
- |---|---|---|
523
- | `hermes` | `nousresearch/hermes-agent:latest` | Hermes agent |
524
- | `hermes-dashboard` | `nousresearch/hermes-agent:latest` | Hermes dashboard |
525
- | `traefik-traefik-1` | `traefik:latest` | Reverse proxy |
526
-
527
- ---
528
-
529
480
  ## Key Gotchas
530
481
 
531
482
  1. **`auth.mode: "none"` + `0.0.0.0:3000`** → OpenClaw refuses to bind. Must be `auth.mode: "token"`.