@flame0510/project-aether 1.1.14 → 1.2.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.
@@ -1,10 +1,24 @@
1
- import { Skeleton, CardRowSkeleton } from '../components/Skeleton';
1
+ import { Page, PageHeader } from '../components/ui';
2
+ import { CredentialCardsSkeleton } from '../components/Skeleton';
2
3
 
4
+ /**
5
+ * Route-level loading UI, shown while the page chunk streams in.
6
+ *
7
+ * It renders the same chrome and the same skeleton as PageClient's own loading
8
+ * state, so the two hand over without a visible change. The previous version
9
+ * used a generic five-row card list at maxWidth 1080, while the real page is a
10
+ * card grid inside the default 1280 — the skeleton and the page it stood for
11
+ * did not line up.
12
+ */
3
13
  const PageSkeleton = () => (
4
- <div style={{ padding: '24px 20px', maxWidth: 1080, margin: '0 auto' }}>
5
- <Skeleton width={120} height={20} accent style={{ marginBottom: 20 }} />
6
- <CardRowSkeleton count={5} height={48} />
7
- </div>
14
+ <Page>
15
+ <PageHeader
16
+ eyebrow=""
17
+ title="Credentials"
18
+ description="Store third-party credentials once, sync to all agent containers."
19
+ />
20
+ <CredentialCardsSkeleton />
21
+ </Page>
8
22
  );
9
23
 
10
24
  export default PageSkeleton;
@@ -1,7 +1,7 @@
1
1
  # Rev4a Architecture — Design & Vision
2
2
 
3
3
  > **Status:** Active — `main` branch
4
- > **Last updated:** 2026-06-30
4
+ > **Last updated:** 2026-08-28
5
5
  > **Goal:** Transform Rev4a from a monitoring dashboard into a central orchestrator for a distributed multi-container agency.
6
6
 
7
7
  ---
@@ -64,15 +64,15 @@
64
64
 
65
65
  ---
66
66
 
67
- ## 2. Current Architecture (as of 2026-06-24)
67
+ ## 2. Current Architecture (as of 2026-08-28)
68
68
 
69
69
  ### What's already been implemented
70
70
 
71
71
  **Container separation:** `openclaw-atlas` already runs as a standalone container with `AGENT_ID=atlas`, discovered dynamically by Rev4a. This proves the container-per-agent model works.
72
72
 
73
- **Vault system:** A credentials vault (`/vault` page) stores API keys and service tokens with per-agent permissions. File-backed vault system (removed - sensitive data is env-only).
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, logs, and quick links. Fully functional.
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.
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
 
@@ -80,8 +80,8 @@
80
80
 
81
81
  | Feature | Status | Notes |
82
82
  |---|---|---|
83
- | Vault (UI + API) | 🟢 Implemented | SQLite + JSON, no encryption yet |
84
- | Container page | 🟢 Functional | Lists all containers, logs, resources |
83
+ | Credentials (UI + API) | 🟢 Implemented | SQLite, no encryption yet |
84
+ | Container page | 🟢 Functional | Lists all containers, resources, 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 |
@@ -103,7 +103,7 @@ The central container, running the Next.js dashboard + orchestration API.
103
103
  - Centralized credential management
104
104
  - Docker socket access for container management
105
105
  - SQLite DB for cross-container event monitoring
106
- - **Onboarding wizard** — first-run setup flow at `/onboarding`, tracks progress in `data/onboarding-progress.json`
106
+ - **First-run wizard** — setup flow at `/wizard`; completion is recorded in `<data dir>/data/wizard.json` and read through `GET /api/wizard/status`
107
107
 
108
108
  **Volume mounts (target):**
109
109
  ```
@@ -120,6 +120,49 @@ The central container, running the Next.js dashboard + orchestration API.
120
120
  - Immutable by agents — enforced by the `:ro` mount
121
121
  - See `lib/agent-setup.ts` for the centralized volume + config guarantee logic
122
122
 
123
+ **Running commands inside containers** (`lib/docker-exec.ts`):
124
+
125
+ This module is how a route reaches an agent container. The migration to it is
126
+ not finished — routes predating it still shell out directly — so treat these as
127
+ the rules for anything you touch, not as a description of the whole tree:
128
+
129
+ - **Never `execSync`/`execFileSync` in a request path.** They block Node's single
130
+ thread: while one runs, no other request is served and no agent's stream
131
+ advances. A `docker exec` costs ~90 ms warm and the OpenClaw CLI costs
132
+ seconds, so a route that walks the fleet freezes the event loop for the sum of
133
+ all of them. Use `dockerExec` / `dockerExecShell` and their `…NoFail` variants.
134
+ - **`mapWithConcurrency` is fail-fast, like `Promise.all`.** If the mapped
135
+ function rejects for one item, the whole call rejects and results already
136
+ computed for other containers are discarded. Fanning out over a fleet requires
137
+ the `…NoFail` variants, or an explicit per-item try/catch, so one unreachable
138
+ container cannot blank an entire page. Callers: `app/api/skills/route.js`,
139
+ `app/api/gateway/route.ts`, `app/api/agents/channels-summary/route.ts`,
140
+ `lib/openclaw-cron.ts`, `app/api/credentials/detect/route.ts`,
141
+ `lib/credentials/delivery.ts`.
142
+ - **Never interpolate a secret into a command string.** Pass it through the `env`
143
+ option and reference it as `"$KEY"`; command substitution inside an
144
+ interpolated value would otherwise execute on the host. Only the *name* reaches
145
+ argv, as `docker exec -e KEY` — docker forwards the value from its own
146
+ environment, so it appears neither in the host process table nor in any
147
+ rejection built from that argv.
148
+ - **A timeout does not reject.** `docker exec` exits 0 on the SIGTERM Node sends
149
+ when the deadline passes, so a command killed halfway *resolves* with whatever
150
+ it had already printed. A `try/catch` therefore cannot tell a truncated read
151
+ from a complete one. Where that distinction matters, make the script prove it
152
+ finished — `detect.ts` emits a `probe:complete` sentinel before its slow calls,
153
+ `containerRead` prints a terminator after the file — and treat its absence as
154
+ failure. Anything that writes back what it read must refuse to write when the
155
+ proof is missing.
156
+ - **A rejection carries `stderr`, never the command.** Node's own message is
157
+ `Command failed: <argv…>`, which reproduces whatever was interpolated into the
158
+ script. `dockerExec` replaces it with `docker exec <container>: <cause> —
159
+ <stderr>`, because these messages travel: `syncProfileToAgents` puts one into
160
+ `SyncResult.error` and the sync route returns it to the browser.
161
+
162
+ `dockerExecWithInput` uses `spawn` rather than `execFile` because
163
+ `promisify(exec)` silently ignores an `input` option: the process starts, stdin
164
+ is never written, and the command hangs with no error to point at.
165
+
123
166
  ### 3.2 Agent Container Template (`openclaw-agent-base`)
124
167
 
125
168
  Docker image for every agent container.
@@ -196,30 +239,34 @@ detailed documentation.
196
239
 
197
240
  ## 4. Credential Management (Vault)
198
241
 
199
- Provider API keys are stored in `data/provider-keys.json`. Third-party service credentials
200
- (GitHub, Trello, Vercel, Supabase) are stored in `data/credentials.db` (SQLite).
201
- At agent spawn time, Rev4a injects only the credentials the agent is permitted to use.
202
-
203
- ```json
204
- {
205
- "providers": {
206
- "openai-codex": "sk-...",
207
- "anthropic": "sk-ant-...",
208
- "groq": "gsk_..."
209
- },
210
- "services": {
211
- "github": { "token": "***", "user": "Flame0510" },
212
- "vercel": { "token": "***", "team": "flame0510" }
213
- },
214
- "agent_permissions": {
215
- "argus": ["providers:all", "services:github", "services:vercel"],
216
- "atlas": ["providers:openai-codex", "services:github"],
217
- "prometheus": ["providers:openai-codex"]
218
- }
219
- }
220
- ```
242
+ Two separate stores:
243
+
244
+ - **Provider API keys** — `<data dir>/data/provider-keys.json`, used by the Rev4a
245
+ provider proxy.
246
+ - **Third-party service credentials** — `<data dir>/data/credentials.db` (SQLite),
247
+ managed from the Credentials page. Five providers: `github-pat`, `trello`,
248
+ `vercel`, `supabase`, `notion` (`lib/credentials/providers.ts`).
249
+
250
+ **Secrets are stored unencrypted.** `credential_secrets.payload` holds raw JSON.
251
+ Anything able to read that file, or to reach `POST /api/credentials/[id]/reveal`
252
+ with a valid session, obtains them in the clear. Encryption (AES-256-GCM, or an
253
+ external vault) is not implemented.
254
+
255
+ **Delivery is explicit, not automatic.** Creating an agent installs nothing: a
256
+ credential reaches a container only when the user syncs it
257
+ (`POST /api/credentials/[id]/sync`), and leaves only on de-sync
258
+ (`DELETE /api/credentials/[id]/sync`). There is no per-agent permission model —
259
+ any stored credential can be synced to any container. Delivery writes each CLI's
260
+ own config inside the container (`lib/credentials/delivery.ts`); OpenClaw's
261
+ `secrets` subsystem covers only OpenClaw's own configuration credentials and
262
+ offers nothing for third-party CLIs.
263
+
264
+ `GET /api/credentials/detect` reports which credentials are actually installed,
265
+ by SHA-256 hashing the token found in each container and matching it against the
266
+ stored profiles.
221
267
 
222
- **Current implementation:** SQLite (`credentials.db`) for services, JSON file (`provider-keys.json`) for provider keys (no encryption yet). Future: encrypted with AES-256-GCM or integrated with HashiCorp Vault / Vaultwarden.
268
+ **Planned:** encryption at rest, and a per-agent permission model so a credential
269
+ can be restricted to a subset of agents.
223
270
 
224
271
  ---
225
272
 
@@ -568,7 +615,7 @@ for the full rationale.
568
615
  | Database | SQLite (WAL mode) | Current `events.db`, may need to scale |
569
616
  | Memory | Per-agent SQLite + central index | New |
570
617
  | Config | Generated YAML/JSON | New |
571
- | Vault | SQLite + JSON | Current: credentials.db + provider-keys.json. Future: encrypted |
618
+ | Credential stores | SQLite + JSON | credentials.db for service tokens, provider-keys.json for provider API keys. Neither is encrypted |
572
619
  | Reverse proxy | Traefik | Already in use |
573
620
  | Monitoring | Rev4a daemon (extended) | Evolution of current daemon |
574
621
  | Version control | Git + GitHub | `github.com/Flame0510/rev4a.git` |
@@ -1,6 +1,6 @@
1
1
  # Rev4a Frontend Architecture
2
2
 
3
- > **Last updated:** 2026-07-19
3
+ > **Last updated:** 2026-08-28
4
4
 
5
5
  ## Layering
6
6
 
@@ -41,11 +41,12 @@ All shared UI primitives live in `app/components/ui/` and are exported from `app
41
41
  | `Page` / `PageHeader` | `Page.tsx` | Full-page layout shell. |
42
42
  | `Icons` | `Icons.tsx` | SVG icons (`EyeIcon`, `EyeOffIcon`), 16/20px shared. |
43
43
  | `PasswordInput` | `PasswordInput.tsx` | Password input with inline show/hide toggle (`<button type="button">` with `aria-label`). |
44
- | `OnboardingPageClient` + step components | `app/onboarding/PageClient.tsx` | 4-step wizard (`WelcomeStep`, `ProvidersStep`, `AgentsStep`, `DoneStep`) + `WizardFrame` shell with mobile-first CSS. |
45
- | `useOnboarding` | `app/onboarding/useOnboarding.ts` | Shared hook: onboarding state, step transitions, provider save, restart. Receives server-side initial state to avoid loading flash. |
46
- | `OnboardingIcons` | `app/onboarding/icons.tsx` | Shared SVG icons (flyweight pattern): `ArrowRightIcon`, `CheckIcon`, `DockerIcon`, `GatewayIcon`, etc. |
44
+ | `WizardPageClient` + step components | `app/wizard/PageClient.tsx` | Multi-step first-run wizard plus its frame shell, with mobile-first CSS. |
45
+ | `useWizard` | `app/wizard/useWizard.ts` | Shared hook: wizard state, step transitions, provider save, restart. Receives server-side initial state to avoid a loading flash. |
46
+ | Wizard icons | `app/wizard/icons.tsx` | Shared SVG icons (flyweight pattern): `ArrowRightIcon`, `CheckIcon`, `CheckCircleIcon`, `DockerIcon`, `GatewayIcon`, `AgentIcon`, `TemplateIcon`, `LinkIcon`, `ConfigIcon`, `InformationIcon`. |
47
47
  | `tokens` | `tokens.ts` | TypeScript types for `Tone` and related token values. |
48
48
  | `Badge` | `Badge.tsx` | Inline status tag with tone variants (success, danger, warning, neutral). Used for channel chips, pairing labels, error/success messages. |
49
+ | `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. |
49
50
 
50
51
  ### Rules
51
52
 
@@ -53,8 +54,12 @@ All shared UI primitives live in `app/components/ui/` and are exported from `app
53
54
  2. **No inline styles on interactive elements.** Buttons, inputs, selects use the `app/components/ui/*` component with component props. Only layout/wrapping containers use inline style.
54
55
  3. **No Italian in code, labels, or comments.** UI strings, error messages, aria-labels — all English.
55
56
  4. **Clickable/custom interactive elements must be `<button type="button">`.** Not `<span onClick>`, not `<div onClick>`. Always include `aria-label` for icon-only buttons.
57
+ 4a. **A control gated on another field stays visible and disabled** — never conditionally unmounted. Rendering it only once its dependency is filled makes it appear out of nowhere, and any hint that references it is pointing at something not on screen. Show it disabled, with the reason beside it, so the shape of the form is stable from the first render.
56
58
  5. **Modals inside forms** — close button has `type="button"` to prevent accidental form submission.
57
- 6. **Loading buttons** — set `loading={true}` on `Button`, do not render separate loaders next to the button. The spinner is built-in.
59
+ 6. **Loading buttons** — set `loading={true}` on `Button`, do not render separate loaders next to the button. The spinner is built-in. The label must also change for the duration (`Saving…`, `Deleting…`, `Syncing…`).
60
+ 6a. **One in-flight mutation, keyed.** Hold a single `busy` string identifying the running action (`delete:<id>`, `sync:<id>:<container>`), plus a `useRef` mirror as the authoritative guard — state is read from the render that produced the click, so two fast clicks would otherwise both pass. `loading` compares against the exact key; `disabled` is set for any busy value, so a control that would be refused looks refused. Never key a mutation on one dimension when the same control is rendered per row of another: a key holding only a container name puts every profile's button for that container into a spinner.
61
+ 6b. **Do not hold the lock across a slow refresh.** Release it when the mutation itself completes and refresh derived state afterwards, unawaited. Show the pending state by fading the stale values only — never the buttons, since `opacity` on an ancestor cannot be undone by a child and makes live controls read as disabled.
62
+ 6c. **An optimistic update moves every field the render derives from, together.** A row that decides its state by reading two fields against each other flips to a third, wrong state if the update touches only one of them — and holds it until the slow refresh lands. Update the whole set the derivation reads, or none of it.
58
63
  7. **New UI component** — add it to `app/components/ui/`, export from `index.ts`, document it here. If it's specific to one page, keep it page-local unless another page needs it.
59
64
 
60
65
  ## GoF pattern mapping
@@ -108,6 +113,9 @@ For interactive pages that can change view/file/tab quickly:
108
113
  2. Guard against stale updates after `await` boundaries.
109
114
  3. On navigation/context switch, kill in-flight requests before starting new ones.
110
115
  4. Mobile layout changes must not wait on network completion.
116
+ 5. After a mutation, refresh the cheap endpoint and await it; fire the expensive one without awaiting. A page that re-reads everything makes the user wait on work their change did not need.
117
+ 6. A refresh helper invoked from a mutation must never reject. The mutation has already succeeded by then, so a failed re-read surfacing as the caller's error reports a completed action as failed.
118
+ 7. Check `res.ok` before reading the body. An error response is still valid JSON, so `json.items ?? []` silently turns a 401 into an empty result presented as fact.
111
119
 
112
120
  ## Migration rule
113
121
 
package/docs/REV4A.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Rev4a — VPS Dashboard
2
2
 
3
- > **Last updated:** 2026-08-09
3
+ > **Last updated:** 2026-09-05
4
4
 
5
5
  A Next.js 16 dashboard for monitoring and managing the OpenClaw ecosystem.
6
6
 
@@ -107,22 +107,21 @@ When environment variables are saved via the Config page (Save & Restart):
107
107
 
108
108
  | Route | Description |
109
109
  |---|---|
110
- | `/dashboard` | System overview — containers, resources, quick links |
110
+ | `/` | System overview — containers, resources, quick links |
111
111
  | `/agents` | Running agents (Docker containers with `AGENT_ID`), gateway token management, agent creation wizard, channel manager (Telegram pairing) |
112
112
  | `/containers` | All Docker containers on the host |
113
- | `/onboarding` | First-run setup wizard (Welcome → Providers → Agent → Ready) |
113
+ | `/wizard` | First-run setup wizard (Welcome → Providers → Agent → Ready) |
114
114
  | `/workspace` | File explorer with tree view + editor — VPS host or container workspaces |
115
115
  | `/lineage` | Agent lineage / orchestration tree |
116
116
  | `/memory` | Agent memory browser |
117
117
  | `/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 |
118
- | `/credentials` | Third-party tool credentials (GitHub, Trello, Vercel, Supabase) — JSON vault with audit trail, sync to agent containers via CLI auth (gh, vercel, supabase) or env files (trello) |
118
+ | `/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 |
119
+ | `/setup` | First-run password setup — sets `REV4A_PASSWORD` and the JWT secret before the dashboard is reachable |
119
120
  | `/plugins` | Plugin manager |
120
- | `/plugins-skills` | Plugin skills |
121
121
  | `/skills` | Skill registry — browse/edit all skills (shared, per-agent workspace, bundled). Promote agent skills to shared with one click. |
122
122
  | `/tools` | Tool configuration |
123
123
  | `/gateway` | LLM provider sync + agent model configuration — see [GATEWAY.md](dev/GATEWAY.md) |
124
124
  | `/config` | Environment management and server restart |
125
- | `/vault` | Credential storage (per-agent permissions) — see [PROVIDERS.md](dev/PROVIDERS.md) |
126
125
  | `/login` | Authentication page |
127
126
 
128
127
  ---
@@ -261,19 +260,29 @@ Container workspaces are discovered dynamically — any Docker container with an
261
260
 
262
261
  ## Features (current)
263
262
 
264
- ### Vault (`/vault`)
265
- Store and manage credentials (API keys, tokens) with per-agent permissions.
266
-
267
- - **SQLite storage:** `credentials.db` in the data directory
268
- - **Provider keys:** stored in `data/provider-keys.json`, managed via the Gateway UI
269
- - **Agent permissions:** each credential can be scoped to specific agents
270
- - **Service management:** add/remove provider API keys and service tokens via the UI
263
+ ### Credentials (`/credentials`)
264
+ Store third-party service credentials and install them into agent containers.
265
+
266
+ - **SQLite storage:** `credentials.db` in the data directory. Secrets are held
267
+ **unencrypted**; anything that can read the file has them in the clear.
268
+ - **Provider keys** are a separate store — `provider-keys.json`, managed from the
269
+ Gateway UI.
270
+ - **Five providers.** GitHub, Vercel and Supabase are handed to their CLI —
271
+ `gh auth login --with-token`, `~/.local/share/com.vercel.cli/auth.json`,
272
+ `supabase login --token`. Trello and Notion have no CLI in the loop: their
273
+ credentials are written to `~/.config/trello/config.json` and
274
+ `~/.config/notion/config.json`, and TOOLS.md tells the agent how to call each
275
+ REST API with them.
276
+ - **Delivery is explicit.** A credential reaches a container only on sync, and
277
+ leaves only on de-sync. There is no per-agent permission model: any stored
278
+ credential can be synced to any container.
279
+ - **Live detection** shows which credentials are actually installed, matching
280
+ SHA-256 hashes of the tokens found in each container against the stored ones.
271
281
 
272
282
  ### Container management (`/containers`)
273
283
  View all running Docker containers with:
274
284
  - Name, image, status, ports, uptime
275
285
  - Resource usage (CPU, memory)
276
- - Per-container logs
277
286
  - Quick links to agent control UIs
278
287
 
279
288
  ### Agent management (`/agents`)