@flame0510/project-aether 1.1.15 → 1.3.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 (60) hide show
  1. package/README.md +2 -1
  2. package/app/agents/ModelSection.tsx +313 -0
  3. package/app/agents/PageClient.tsx +83 -4
  4. package/app/agents/create/page.tsx +8 -21
  5. package/app/api/agents/[id]/model/route.ts +113 -0
  6. package/app/api/agents/[id]/recreate/route.ts +10 -34
  7. package/app/api/agents/[id]/route.ts +10 -29
  8. package/app/api/agents/create/route.ts +59 -57
  9. package/app/api/agents/models-summary/route.ts +163 -0
  10. package/app/api/assistant/route.ts +36 -15
  11. package/app/api/credentials/[id]/sync/route.ts +3 -3
  12. package/app/api/credentials/detect/route.ts +126 -176
  13. package/app/api/credentials/route.ts +3 -0
  14. package/app/api/gateway/agent/route.ts +23 -6
  15. package/app/api/gateway/provider/keys.ts +13 -1
  16. package/app/api/gateway/provider/route.ts +43 -12
  17. package/app/api/gateway/sync.ts +248 -72
  18. package/app/api/models/route.ts +28 -34
  19. package/app/api/provider/auth.ts +65 -0
  20. package/app/api/provider/upstream.ts +9 -2
  21. package/app/api/provider/v1/chat/completions/route.ts +22 -16
  22. package/app/api/provider/v1/models/route.ts +26 -133
  23. package/app/components/PulseChat.tsx +25 -39
  24. package/app/components/Skeleton.tsx +132 -0
  25. package/app/components/ui/RemoveButton.tsx +46 -0
  26. package/app/components/ui/Select.tsx +3 -2
  27. package/app/components/ui/index.ts +1 -0
  28. package/app/credentials/PageClient.tsx +461 -140
  29. package/app/credentials/loading.tsx +19 -5
  30. package/app/gateway/PageClient.tsx +257 -673
  31. package/app/globals.css +8 -0
  32. package/app/lib/models-context.tsx +43 -7
  33. package/app/wizard/useWizard.ts +6 -1
  34. package/bin/rev4a.js +73 -9
  35. package/docs/ARCHITECTURE.md +92 -33
  36. package/docs/FRONTEND-ARCHITECTURE.md +36 -6
  37. package/docs/REV4A.md +62 -30
  38. package/docs/dev/API-REFERENCE.md +490 -227
  39. package/docs/dev/DATABASE.md +8 -3
  40. package/docs/dev/GATEWAY.md +236 -92
  41. package/docs/dev/PROVIDERS.md +44 -44
  42. package/docs/rag/DATA-FRESHNESS.md +57 -28
  43. package/docs/rag/GLOSSARY.md +20 -18
  44. package/docs/rag/REV4A-OVERVIEW.md +28 -32
  45. package/docs/rag/WHAT-I-CAN-ANSWER.md +10 -12
  46. package/instrumentation.ts +9 -1
  47. package/lib/agent-readiness.ts +110 -0
  48. package/lib/channelManager.ts +64 -22
  49. package/lib/container-file.ts +27 -0
  50. package/lib/credentials/delivery.ts +212 -119
  51. package/lib/credentials/detect.ts +229 -97
  52. package/lib/credentials/providers.ts +38 -7
  53. package/lib/credentials/vault.ts +78 -13
  54. package/lib/docker-exec.ts +50 -14
  55. package/lib/model-catalogue.ts +140 -27
  56. package/lib/rev4a-paths.ts +0 -21
  57. package/model-pricing.json +118 -110
  58. package/models.config.json +27 -12
  59. package/package.json +1 -1
  60. package/app/api/gateway/route.ts +0 -191
@@ -1,6 +1,6 @@
1
1
  # Rev4a API Reference
2
2
 
3
- > **Last updated:** 2026-07-19
3
+ > **Last updated:** 2026-09-14
4
4
 
5
5
  All routes are under `/api/`. Authentication is required on every endpoint
6
6
  unless otherwise noted.
@@ -68,100 +68,134 @@ Delete the `rev4a_token` cookie.
68
68
 
69
69
  ---
70
70
 
71
- ## Onboarding
71
+ ### `GET /api/auth/status`
72
+ Whether a password has been configured, in the environment or in the `.env` the
73
+ setup wizard writes. Used by the frontend to choose between the setup wizard and
74
+ the login form.
72
75
 
73
- ### `GET /api/onboarding/status`
74
- Returns per-step onboarding progress.
76
+ **Auth:** none — it reveals only whether setup has happened, never the password
75
77
 
76
- **Auth:** browser cookie
78
+ **Response:** `{ "configured": true }`
77
79
 
78
- **Response:**
79
- ```json
80
- {
81
- "complete": false,
82
- "steps": { "welcome": true, "providers": false, "agents": false },
83
- "completedAt": null
84
- }
85
- ```
80
+ ---
86
81
 
87
- If no progress file exists, returns all steps as `false`.
82
+ ## Setup & wizard
88
83
 
89
- ### `POST /api/onboarding/complete`
90
- Mark a step as completed, complete all steps, or reset the wizard.
84
+ ### `GET /api/wizard/status`
85
+ Whether the first-run wizard has been completed.
91
86
 
92
- **Auth:** browser cookie
87
+ **Auth:** browser cookie or bearer token
93
88
 
94
- **Body:**
95
- | Shape | Effect |
96
- |---|---|
97
- | `{ "step": "providers" }` | Mark a single step as done |
98
- | `{}` | Mark all steps as done (complete onboarding) |
99
- | `{ "reset": true }` | Reset all steps to `false` (restart wizard) |
89
+ **Response:** `{ "done": true }`
100
90
 
101
- **Response:**
102
- ```json
103
- {
104
- "status": "ok",
105
- "steps": { "welcome": true, "providers": true, "agents": false },
106
- "complete": false
107
- }
108
- ```
91
+ ### `POST /api/wizard/complete`
92
+ Mark the wizard as completed.
93
+
94
+ **Auth:** browser cookie or bearer token
95
+
96
+ **Response:** `{ "done": true }`
97
+
98
+ ### `POST /api/wizard/reset`
99
+ Reset the wizard so it runs again.
100
+
101
+ **Auth:** browser cookie or bearer token
102
+
103
+ **Response:** `{ "done": false }`
109
104
 
110
105
  ---
111
106
 
112
- ## Gateway
107
+ ### `POST /api/setup/password`
108
+ Set the dashboard password during first-run setup.
113
109
 
114
- The Gateway API reads the model catalogue from [`models.config.json`](../models.config.json)
115
- (tracked in the repository). Full documentation at [GATEWAY.md](GATEWAY.md) and
116
- [PROVIDERS.md](PROVIDERS.md).
110
+ **Auth:** none, and only usable once — returns `409` if a password already exists
117
111
 
118
- ### `GET /api/gateway`
119
- Returns live gateway status from all agent containers.
112
+ **Body:** `{ "password": "..." }` — minimum 8 characters
120
113
 
121
- **Auth:** any auth method
114
+ **Errors:** `400` invalid JSON, missing password, or shorter than 8 characters;
115
+ `409` already configured; `500` the `.env` file could not be written.
122
116
 
123
- **Response:**
124
- ```json
125
- {
126
- "agents": {
127
- "total": 1,
128
- "list": [
129
- {
130
- "containerName": "openclaw-atlas",
131
- "agentId": "atlas",
132
- "agentName": "Atlas",
133
- "defaultModel": "rev4a/deepseek/deepseek-v4-flash",
134
- "configured": true
135
- }
136
- ]
137
- }
138
- }
139
- ```
117
+ **Response:** `{ "status": "ok" }`
118
+
119
+ ### `POST /api/setup/restart`
120
+ Restart the service so a freshly written `.env` takes effect.
121
+
122
+ **Auth:** open before a password exists, then browser cookie or bearer token (`requireAuthIfConfigured`)
123
+
124
+ **Errors:** `500` if the `.env` file is missing, or the restart could not be issued.
125
+
126
+ ### `GET /api/setup/agent-image`
127
+ Whether the `openclaw-agent-base` image is present.
128
+
129
+ **Auth:** open before a password exists, then browser cookie or bearer token (`requireAuthIfConfigured`)
130
+
131
+ **Response:** `{ "exists": true, "imageId": "sha256:..." }`, or `{ "exists": false }`
132
+ when the image is absent **or Docker is unreachable** — the two are not distinguished.
133
+
134
+ ### `POST /api/setup/agent-image`
135
+ Pull or build the agent base image, streaming progress.
136
+
137
+ **Auth:** open before a password exists, then browser cookie or bearer token (`requireAuthIfConfigured`)
138
+
139
+ **Errors:** `409` if a download is already running.
140
+
141
+ ---
142
+
143
+ ## Gateway
144
+
145
+ The Gateway API reads the model catalogue from [`models.config.json`](../../models.config.json)
146
+ (tracked in the repository). Full documentation at [GATEWAY.md](GATEWAY.md) and
147
+ [PROVIDERS.md](PROVIDERS.md).
140
148
 
141
149
  ### `PUT /api/gateway/provider`
142
- Trigger a full provider model sync to all agent containers.
150
+ Enable or disable a single model in the catalogue, then sync every agent.
143
151
 
144
- **Auth:** any auth method
152
+ **Auth:** browser cookie or bearer token
145
153
 
146
- **Body:** none (reads current provider state from `models.config.json` and `data/provider-keys.json`)
154
+ **Body:** `{ "modelId": "deepseek/deepseek-flash", "enabled": true }` — both
155
+ required. Missing either returns `400`; an unknown `modelId` returns `404`.
156
+ If Docker cannot be reached the toggle is refused with `503` and nothing is saved,
157
+ so a whole-fleet outage never leaves the checkboxes out of step with the agents.
147
158
 
148
- **Response:**
159
+ **Response:** `status` is the toggle, which is saved before the sync runs. `sync`
160
+ reports separately whether the agents received it.
149
161
  ```json
150
162
  {
151
163
  "status": "ok",
152
- "activeModels": 2,
153
- "results": ["Active models: 2 across 1 agent(s)"]
164
+ "modelId": "deepseek/deepseek-flash",
165
+ "enabled": true,
166
+ "provider": "deepseek",
167
+ "sync": {
168
+ "ok": true,
169
+ "activeModels": 3,
170
+ "total": 2,
171
+ "synced": ["agent_2a3c3a07", "agent_9253eee3"],
172
+ "stopped": [],
173
+ "failed": [],
174
+ "summary": "Synced 2 of 2 agent(s)."
175
+ }
154
176
  }
155
177
  ```
156
178
 
179
+ `sync.ok` is true only when every agent container received the catalogue; zero
180
+ agents counts as ok. "Every" includes agents that are not running (stopped, paused,
181
+ created): they are written through `docker cp`, listed in `stopped` as well as
182
+ `synced`, and apply the change when started or resumed. A container whose config
183
+ cannot be read is listed in `failed` and left untouched — the sync never writes a
184
+ config it did not read. When Docker cannot be reached, `total` is `0`, `dockerError`
185
+ carries the cause and `ok` is false. For this toggle that case is refused up front
186
+ with `503`, so it appears here only if Docker drops between the check and the sync;
187
+ on a provider key save (`POST`) it is reported, not refused. `configError` is set,
188
+ and nothing is attempted, when the gateway key is missing or `models.config.json`
189
+ could not be read.
190
+
157
191
  **Side effects:**
158
192
  - Writes `models.providers.rev4a` to each agent container's `openclaw.json`
159
193
  - Cleans up stale `models.json` and `auth-profiles.json` inside containers
160
- - **Does NOT touch `agents.defaults.model` or `agents.list[].model`** — model references are managed per-container via the Agents Tab (`PUT /api/gateway/agent`)
194
+ - **Does NOT touch `agents.defaults.model` or `agents.list[].model`** — model references are managed per-container from the agent detail panel (`PUT /api/gateway/agent`)
161
195
  - Does NOT restart the gateway (config is written live to the file)
162
196
 
163
197
  ### `PUT /api/gateway/agent`
164
- Update model config for a single agent container.
198
+ Update the primary model and fallbacks of a single agent container.
165
199
 
166
200
  **Auth:** any auth method
167
201
 
@@ -169,28 +203,38 @@ Update model config for a single agent container.
169
203
  ```json
170
204
  {
171
205
  "containerName": "openclaw-atlas",
172
- "model": "deepseek/deepseek-v4-flash",
173
- "fallbacks": []
206
+ "model": "deepseek/deepseek-flash",
207
+ "fallbacks": ["deepseek/deepseek-v4-pro"]
174
208
  }
175
209
  ```
176
- If `fallbacks` is omitted or empty, no fallback models are set.
210
+ `fallbacks` omitted and `fallbacks: []` are different requests. Omitted leaves the
211
+ container's existing fallbacks untouched; `[]` clears them.
177
212
 
178
- **Response:**
213
+ `containerName` is used verbatim as the `docker exec` target, not resolved from the
214
+ `AGENT_ID` label. Every container's name currently equals its label, so an agent id
215
+ works.
216
+
217
+ **Response:** reports what was written, with the `rev4a/` prefix applied.
179
218
  ```json
180
219
  {
181
220
  "status": "ok",
182
221
  "agent": "openclaw-atlas",
183
222
  "model": {
184
- "primary": "rev4a/deepseek/deepseek-v4-flash"
223
+ "primary": "rev4a/deepseek/deepseek-flash",
224
+ "fallbacks": ["rev4a/deepseek/deepseek-v4-pro"]
185
225
  },
186
226
  "verify": { "ok": true, "bytes": 2058 }
187
227
  }
188
228
  ```
189
229
 
190
230
  **Behaviour:**
191
- - Writes `agents.defaults.model.primary` and `agents.list[0].model.primary` with format `rev4a/<provider>/<model>`
192
- - No restart — writes are live via `docker exec node -e`
193
- - Does NOT set fallbacks unless explicitly provided
231
+ - Writes `agents.defaults.model`, and the entry in `agents.list` whose `id` is
232
+ `main` (skipped if there is none), both as `{ primary, fallbacks }`. The primary is
233
+ removed from its own fallback list.
234
+ - Rewrites the whole `openclaw.json` with `docker exec … cat >`. No gateway restart:
235
+ OpenClaw hot-applies the change.
236
+ - Does not check the model against the catalogue. The proxy does that when the model
237
+ is used, so a model saved here while disabled is refused at request time.
194
238
 
195
239
  ### `GET /api/gateway/provider`
196
240
  Returns current provider configuration state.
@@ -213,7 +257,7 @@ Returns current provider configuration state.
213
257
  "baseUrl": "https://api.deepseek.com",
214
258
  "docsUrl": "https://platform.deepseek.com/api_keys",
215
259
  "models": [
216
- { "id": "deepseek/deepseek-v4-flash", "name": "DeepSeek V4 Flash", "enabled": true },
260
+ { "id": "deepseek/deepseek-flash", "name": "DeepSeek Flash", "enabled": true },
217
261
  { "id": "deepseek/deepseek-v4-pro", "name": "DeepSeek V4 Pro", "enabled": true }
218
262
  ]
219
263
  }
@@ -223,7 +267,8 @@ Returns current provider configuration state.
223
267
 
224
268
  ### `GET /api/provider/v1/models`
225
269
 
226
- Rev4a Provider Gateway — returns available models filtered by auth token.
270
+ Rev4a Provider Gateway — returns the models this deployment offers (enabled after
271
+ overrides, provider has a key). A missing or wrong token gets an empty list, not a 401.
227
272
 
228
273
  **Auth:** Bearer token (must match `rev4a` key in `data/provider-keys.json`)
229
274
 
@@ -233,7 +278,7 @@ Rev4a Provider Gateway — returns available models filtered by auth token.
233
278
  "object": "list",
234
279
  "total": 9,
235
280
  "data": [
236
- { "id": "deepseek/deepseek-v4-flash", "object": "model", "created": 1700000000, "owned_by": "deepseek", "permission": [], "root": "deepseek/deepseek-v4-flash" }
281
+ { "id": "deepseek/deepseek-flash", "object": "model", "created": 1700000000, "owned_by": "deepseek", "permission": [], "root": "deepseek/deepseek-flash" }
237
282
  ]
238
283
  }
239
284
  ```
@@ -245,6 +290,10 @@ Rev4a Provider Gateway — returns available models filtered by auth token.
245
290
 
246
291
  ### `POST /api/provider/v1/chat/completions`
247
292
 
293
+ A model the deployment does not offer is refused with `400` before anything reaches
294
+ upstream. Routing fields that would let the upstream choose another model — `models`,
295
+ `route`, `preset` — are removed from the body before forwarding.
296
+
248
297
  Rev4a Provider Gateway — proxy chat completions to the correct upstream.
249
298
 
250
299
  **Auth:** Bearer token (must match `rev4a` key in `data/provider-keys.json`)
@@ -252,7 +301,7 @@ Rev4a Provider Gateway — proxy chat completions to the correct upstream.
252
301
  **Body:**
253
302
  ```json
254
303
  {
255
- "model": "rev4a/deepseek-v4-flash",
304
+ "model": "rev4a/deepseek-flash",
256
305
  "messages": [{"role": "user", "content": "Hello"}]
257
306
  }
258
307
  ```
@@ -264,6 +313,79 @@ Rev4a Provider Gateway — proxy chat completions to the correct upstream.
264
313
 
265
314
  ---
266
315
 
316
+ ### `GET /api/models`
317
+ Models eligible to be used: the catalogue filtered down to models that are enabled
318
+ (after overrides) and whose provider has a key configured, grouped by provider. Feeds the model pickers in the agent's
319
+ Model panel.
320
+
321
+ **Auth:** browser cookie or bearer token
322
+
323
+ **Response:**
324
+ ```json
325
+ [
326
+ {
327
+ "provider": "DeepSeek",
328
+ "emoji": "🧠",
329
+ "models": [
330
+ {
331
+ "id": "deepseek/deepseek-flash",
332
+ "label": "DeepSeek Flash (V4.1)",
333
+ "input": 0.3,
334
+ "output": 1.2,
335
+ "deprecated": false
336
+ }
337
+ ]
338
+ }
339
+ ]
340
+ ```
341
+
342
+ `input` and `output` are USD per 1M tokens, `null` when unpriced. `deprecated`
343
+ marks a name upstream has retired; it stays selectable and the pickers tag it.
344
+ See [GATEWAY.md](GATEWAY.md) for the deprecation lifecycle.
345
+
346
+ ### `GET /api/gateway/provider/balance`
347
+ Remaining credit for one provider.
348
+
349
+ **Auth:** browser cookie or bearer token
350
+
351
+ **Query params:** `provider` — one of `deepseek`, `openrouter`, `glm`.
352
+
353
+ **Errors:** `502` when the provider's API cannot be reached or rejects the call.
354
+
355
+ ### `POST /api/gateway/provider/oauth`
356
+ Run the OAuth device flow for a provider that supports it.
357
+
358
+ **Auth:** browser cookie or bearer token
359
+
360
+ **Body:** `{ "provider": "..." }`
361
+
362
+ **Errors:** `400` for an unknown provider; the response body carries `available`
363
+ with the providers that do support OAuth.
364
+
365
+ ### `POST /api/aliases/add`
366
+ Add a model alias.
367
+
368
+ **Auth:** browser cookie or bearer token
369
+
370
+ **Body:** `{ "alias": "fast", "model": "deepseek/deepseek-chat" }`
371
+
372
+ **Response:** `{ "ok": true, "output": "..." }` — `output` is the CLI's stdout.
373
+
374
+ **Errors:** `400` if `alias` or `model` is missing.
375
+
376
+ ### `POST /api/aliases/remove`
377
+ Remove a model alias.
378
+
379
+ **Auth:** browser cookie or bearer token
380
+
381
+ **Body:** `{ "alias": "fast" }`
382
+
383
+ **Response:** `{ "ok": true, "output": "..." }`
384
+
385
+ **Errors:** `400` if `alias` is missing.
386
+
387
+ ---
388
+
267
389
  ## Config
268
390
 
269
391
  ### `PUT /api/config/env`
@@ -344,6 +466,17 @@ Returns a single session with its events and child sessions.
344
466
 
345
467
  ---
346
468
 
469
+ ### `GET /api/sessions/[id]`
470
+ One session with its events and any child sessions.
471
+
472
+ **Auth:** browser cookie or bearer token
473
+
474
+ **Response:** `{ "session": {…}, "events": [...], "children": [...] }`
475
+
476
+ **Errors:** `404` if the session does not exist.
477
+
478
+ ---
479
+
347
480
  ## Stats & Costs
348
481
 
349
482
  ### `GET /api/stats`
@@ -553,6 +686,7 @@ List running agent containers (Docker containers with `AGENT_ID` label) with ful
553
686
  "controlPort": "3033"
554
687
  }
555
688
  ]
689
+ ```
556
690
 
557
691
  **Notes:**
558
692
  - The URL is always `http://<host-ip>:<port>#token=...` (every agent gets a host port mapping)
@@ -562,6 +696,77 @@ List running agent containers (Docker containers with `AGENT_ID` label) with ful
562
696
 
563
697
  ---
564
698
 
699
+ ### `GET /api/agents/models-summary`
700
+ One row per agent container, running or not: the model it runs and whether that
701
+ model is still in good standing. Feeds the model chip on the agent cards. Running
702
+ agents are read with `docker exec`, others with `docker cp`. If Docker cannot be
703
+ reached the route answers `503` with an `error`, not an empty list.
704
+
705
+ **Auth:** any auth method
706
+
707
+ **Response:**
708
+ ```json
709
+ {
710
+ "agents": [
711
+ {
712
+ "agentId": "atlas",
713
+ "containerName": "openclaw-atlas",
714
+ "primary": "rev4a/deepseek/deepseek-flash",
715
+ "fallbackCount": 0,
716
+ "firstFallback": null,
717
+ "health": "ok"
718
+ }
719
+ ]
720
+ }
721
+ ```
722
+
723
+ `health`, ordered by the remedy each needs:
724
+
725
+ | value | meaning | remedy |
726
+ |---|---|---|
727
+ | `missing` | the primary is not in the catalogue at all | pick another model |
728
+ | `disabled` | in the catalogue but not offered — unchecked, or its provider has no key; the proxy refuses it | pick another model |
729
+ | `out-of-sync` | offered by the gateway, absent from this container's synced copy | Sync All Agents |
730
+ | `deprecated` | works, on a name upstream has retired | switch when convenient |
731
+ | `ok` | usable | — |
732
+ | `unset` | no primary configured | — |
733
+ | `unknown` | the container did not answer | — |
734
+
735
+ ### `GET /api/agents/[id]/model`
736
+ The model configuration of one agent, read from that container alone rather than
737
+ by walking the fleet. Writes still go to `PUT /api/gateway/agent`.
738
+
739
+ **Auth:** any auth method
740
+
741
+ **Response:**
742
+ ```json
743
+ {
744
+ "agentId": "atlas",
745
+ "reachable": true,
746
+ "primary": "rev4a/deepseek/deepseek-flash",
747
+ "fallbacks": [],
748
+ "resolvable": true,
749
+ "catalogueSize": 41,
750
+ "primaryStatus": "offered"
751
+ }
752
+ ```
753
+
754
+ `resolvable` is tri-state. `false` means the primary is absent from the catalogue
755
+ synced into that container, usually because the model was disabled on the Gateway
756
+ page while this agent still pointed at it. `true` means it is present. `null` means
757
+ the question does not apply: either the container did not answer, or no primary is
758
+ set.
759
+
760
+ When `reachable` is `false` the container did not answer, `primary` and `fallbacks`
761
+ come back empty rather than read, and **`catalogueSize` is omitted entirely** — it
762
+ is present only on the reachable branch.
763
+
764
+ `primaryStatus`, also reachable branch only, says why the primary would fail
765
+ regardless of this container: `offered`, `disabled` (unchecked, or its provider has
766
+ no key) or `missing` (no longer in the catalogue). `null` when no primary is set.
767
+ `resolvable` answers a different question — whether *this container's* synced copy
768
+ has it — so `offered` with `resolvable: false` means the container is out of sync.
769
+
565
770
  ### `POST /api/agents/create`
566
771
  Create a new agent container from a template.
567
772
 
@@ -573,7 +778,7 @@ Create a new agent container from a template.
573
778
  "name": "my-agent",
574
779
  "template": "prometheus",
575
780
  "portRange": "3700-3709",
576
- "model": "deepseek/deepseek-v4-flash",
781
+ "model": "deepseek/deepseek-flash",
577
782
  "fallbacks": []
578
783
  }
579
784
  ```
@@ -583,7 +788,7 @@ Create a new agent container from a template.
583
788
  | `name` | yes | Container name, also becomes `AGENT_ID` and subdomain |
584
789
  | `template` | yes | Template name (directory in `agent-templates/`) |
585
790
  | `portRange` | no | Optional port range (e.g. `3700-3709`) or single port (e.g. `3700`). Default: auto-assigned 10-port block |
586
- | `model` | no | Primary model ID from `models.config.json` |
791
+ | `model` | no | Primary model id. Must be offered by this deployment (in the catalogue, enabled, provider has a key), in any form the proxy accepts; otherwise `400`. Defaults to `rev4a/deepseek/deepseek-flash`, validated the same way |
587
792
  | `fallbacks` | no | Array of fallback model IDs |
588
793
 
589
794
  **Every agent is created with a host port mapping:**
@@ -610,11 +815,16 @@ Create a new agent container from a template.
610
815
 
611
816
  **Post-creation pipeline:**
612
817
  1. OpenClaw gateway starts with `--allow-unconfigured`, generating its own default config
613
- 2. The route waits for the gateway to be fully up (health check poll, up to 60 s)
614
- 3. Once ready, writes `gateway.controlUi.allowedOrigins` + `agents.defaults.model.primary` + fallbacks
615
- 4. Writes `models.providers.rev4a` + extraDirs + update config → single `openclaw.json` write → single `gateway restart`
818
+ 2. The route waits for the gateway to finish starting (`waitForGatewayReady`, up to 60 s)
819
+ 3. Once ready, rewrites `openclaw.json` in one write — `gateway.controlUi`,
820
+ `agents.defaults.model` (primary and fallbacks, always as `rev4a/<provider>/<model>`)
821
+ and `update` — then runs `openclaw gateway restart`
822
+ 4. Applies the runtime config (hooks, shared skills) and writes `models.providers.rev4a`
823
+ with `openclaw config patch --stdin`. No restart for this step: `gateway.reload`
824
+ defaults to `hybrid`, which hot-applies changes under `agents`, `models` and
825
+ `routing`. If the catalogue cannot be read the provider patch is skipped and logged
616
826
 
617
- > **Note:** The entrypoint no longer generates `openclaw.json`. All post-creation config is written by the create route after gateway readiness, eliminating the race condition where the entrypoint would overwrite synced provider config.
827
+ Failures in steps 3 and 4 do not fail the request.
618
828
 
619
829
  **Side effects:**
620
830
  - Template files (`AGENTS.md`, `SOUL.md`, etc.) are copied into the container workspace via `docker cp`
@@ -762,6 +972,21 @@ aligning the volume's state/config to the new binary.
762
972
 
763
973
  ---
764
974
 
975
+ ### `POST /api/agents/[id]/lifecycle`
976
+ Start, stop or restart an agent container.
977
+
978
+ **Auth:** browser cookie or bearer token
979
+
980
+ **Body:** `{ "action": "start" | "stop" | "restart" }`
981
+
982
+ Redundant actions are no-ops: starting a running container, or stopping a stopped
983
+ one, succeeds without touching it.
984
+
985
+ **Errors:** `400` for a malformed body, an unknown action, or an invalid agent id;
986
+ `404` when no container carries that `AGENT_ID`.
987
+
988
+ ---
989
+
765
990
  ## Channels
766
991
 
767
992
  Channel APIs read/write agent channel configuration (Telegram) directly inside agent containers via `docker exec`. All endpoints require the agent container to be running.
@@ -935,7 +1160,7 @@ Useful for inspecting the container config without SSH or terminal.
935
1160
  "name": "my-agent",
936
1161
  "config": {
937
1162
  "gateway": { "controlUi": { "allowedOrigins": ["..."], "dangerouslyDisableDeviceAuth": true } },
938
- "agents": { "defaults": { "model": { "primary": "rev4a/deepseek/deepseek-v4-flash", "fallbacks": [] } } },
1163
+ "agents": { "defaults": { "model": { "primary": "rev4a/deepseek/deepseek-flash", "fallbacks": [] } } },
939
1164
  "models": { "providers": { "rev4a": { "baseUrl": "...", "apiKey": "***", "models": [...] } } }
940
1165
  }
941
1166
  }
@@ -987,7 +1212,10 @@ Update the shared agents gateway token and push it to all running agent containe
987
1212
  ### `GET /api/agents/image-status`
988
1213
  Check the status of the `openclaw-agent-base` Docker image.
989
1214
 
990
- **Auth:** public (no auth required)
1215
+ **Auth:** browser cookie or bearer token
1216
+
1217
+ The ghcr.io digest lookup is cached for 10 minutes and de-duplicated while in
1218
+ flight, so polling this endpoint does not re-query the registry every time.
991
1219
 
992
1220
  **Response:**
993
1221
  ```json
@@ -1011,7 +1239,7 @@ Starts pulling or building the `openclaw-agent-base:latest` image. Returns 202
1011
1239
  Accepted immediately — the operation runs in the background and writes logs.
1012
1240
  The frontend polls `GET /api/agents/image-status` for progress updates.
1013
1241
 
1014
- **Auth:** public (no auth required)
1242
+ **Auth:** browser cookie or bearer token
1015
1243
 
1016
1244
  **Response (202):**
1017
1245
  ```json
@@ -1023,17 +1251,7 @@ The frontend polls `GET /api/agents/image-status` for progress updates.
1023
1251
  { "error": "A download is already in progress" }
1024
1252
  ```
1025
1253
 
1026
- ### `DELETE /api/agents/abort-download`
1027
- Kills an in-progress docker pull/build.
1028
-
1029
- **Auth:** public (no auth required)
1030
-
1031
- **Response:**
1032
- ```json
1033
- { "aborted": true }
1034
- ```
1035
-
1036
- Image state is managed in `lib/buildAgentImage.ts`:
1254
+ Image state is managed in `lib/buildAgentImage.ts` (library functions, not HTTP routes):
1037
1255
  - `downloadAgentImage(opts)` — attempts `docker pull` from registry first, then falls back to `docker build`
1038
1256
  all stdout/stderr to a log file, supports optional `onEvent` callback and `AbortSignal`
1039
1257
  - `abortDownload()` — sends SIGKILL to the child process, releases the download lock
@@ -1274,7 +1492,7 @@ Run history for a specific cron job. Queries the container's gateway via `opencl
1274
1492
  "error": "Channel is required ...",
1275
1493
  "runAtMs": 1784820025581,
1276
1494
  "durationMs": 11046,
1277
- "model": "deepseek/deepseek-v4-flash",
1495
+ "model": "deepseek/deepseek-flash",
1278
1496
  "usage": { "input_tokens": 15872, "output_tokens": 597 }
1279
1497
  }
1280
1498
  ]
@@ -1361,93 +1579,23 @@ Write content to a workspace file.
1361
1579
 
1362
1580
  ---
1363
1581
 
1364
- ## Vault
1365
-
1366
- Full key management documentation at [PROVIDERS.md](PROVIDERS.md).
1367
-
1368
- ### `GET /api/vault`
1369
- List all stored credentials (keys masked).
1370
-
1371
- **Auth:** browser cookie
1372
-
1373
- **Response:**
1374
- ```json
1375
- {
1376
- "providers": {
1377
- "deepseek": { "keyStatus": "present", "scoped": ["atlas"] }
1378
- },
1379
- "services": {
1380
- "github": { "tokenStatus": "present", "user": "Flame0510", "scoped": ["argus", "atlas"] }
1381
- }
1382
- }
1383
- ```
1384
-
1385
- ### `GET /api/vault/provider/key`
1386
- Return the full API key for a provider.
1387
-
1388
- **Auth:** browser cookie
1389
-
1390
- **Query params:** `provider` (required), `agent` (optional)
1391
-
1392
- **Response (200):**
1393
- ```json
1394
- {
1395
- "provider": "deepseek",
1396
- "apiKey": "sk-...full-key...",
1397
- "masked": "sk-...be03",
1398
- "source": "local"
1399
- }
1400
- ```
1401
-
1402
- **Error:** `400` if `provider` missing; `404` if key not found.
1403
-
1404
- See [PROVIDERS.md](PROVIDERS.md#get-apivaultproviderkey) for full detail.
1405
-
1406
- ### `POST /api/vault/provider`
1407
- Add or update a provider API key.
1408
-
1409
- **Auth:** browser cookie
1410
-
1411
- **Body:** `{ "provider": "deepseek", "apiKey": "sk-...", "baseUrl": "https://api.deepseek.com" }`
1412
-
1413
- **Response:** `{ "status": "ok", "provider": "deepseek", "masked": "sk-...be03", "updatedAt": 1749200000000 }`
1414
-
1415
- ### `DELETE /api/vault/provider`
1416
- Remove a provider and all its keys.
1417
-
1418
- **Auth:** browser cookie
1419
-
1420
- **Body:** `{ "provider": "deepseek" }`
1421
-
1422
- **Response:** `{ "status": "removed" }`
1423
-
1424
- ### `PUT /api/vault/service`
1425
- Add or update a service token.
1426
-
1427
- **Auth:** browser cookie
1428
-
1429
- **Body:** `{ "id": "github", "token": "***", "user": "Flame0510", "scopes": ["atlas"] }`
1430
-
1431
- **Response:** `{ "ok": true }`
1432
-
1433
- ### `DELETE /api/vault/service`
1434
- Remove a service.
1435
-
1436
- **Auth:** browser cookie
1582
+ ## Provider keys and credentials
1437
1583
 
1438
- **Body:** `{ "id": "github" }`
1584
+ Provider API keys are read with `GET /api/gateway/provider` and set with
1585
+ `POST /api/gateway/provider` — documented in `docs/dev/PROVIDERS.md`, which is the
1586
+ only file carrying that endpoint's request bodies. `PUT` toggles a model, not a
1587
+ key. The keys are stored in `data/provider-keys.json`.
1588
+ Third-party service credentials are managed under `/api/credentials`
1589
+ (documented below) and stored in `credentials.db`.
1439
1590
 
1440
- **Response:** `{ "ok": true }`
1441
-
1442
- ### `PUT /api/vault/permissions`
1443
- Update agent permissions for a credential.
1591
+ There is no `/api/vault` route, and no per-credential agent scoping: any stored
1592
+ credential can be synced to any container.
1444
1593
 
1445
- **Body:** `{ "type": "provider" | "service", "id": "deepseek", "scopes": ["atlas", "argus"] }`
1446
-
1447
- **Response:** `{ "ok": true }`
1594
+ Full key management documentation at [PROVIDERS.md](PROVIDERS.md).
1448
1595
 
1449
1596
  ---
1450
1597
 
1598
+
1451
1599
  ## Containers
1452
1600
 
1453
1601
  ### `GET /api/containers`
@@ -1472,24 +1620,13 @@ List all running Docker containers with resource usage.
1472
1620
  }
1473
1621
  ```
1474
1622
 
1475
- ### `GET /api/containers/logs?name=<name>&tail=<lines>`
1476
- Get container logs.
1477
-
1478
- **Auth:** browser cookie
1479
-
1480
- **Query params:** `name` (required), `tail` (default 50)
1481
-
1482
- **Response:** `{ "name": "openclaw-atlas", "logs": "[log lines...]" }`
1483
-
1484
- ### `POST /api/containers/action`
1485
- Execute action on a container.
1486
-
1487
- **Body:** `{ "action": "restart" | "stop" | "start", "name": "openclaw-atlas" }`
1488
-
1489
- **Response:** `{ "ok": true, "message": "Container openclaw-atlas restarted" }`
1623
+ The container list is the only container route. There is no logs endpoint and no
1624
+ action endpoint: logs are read through the terminal WebSocket, and lifecycle
1625
+ operations on agent containers go through `/api/agents/[id]/lifecycle`.
1490
1626
 
1491
1627
  ---
1492
1628
 
1629
+
1493
1630
  ## Tools
1494
1631
 
1495
1632
  ### `GET /api/tools-config`
@@ -1522,30 +1659,16 @@ Return the installed OpenClaw CLI version.
1522
1659
 
1523
1660
  **Fallback:** `{ "version": "unknown" }` if the command fails.
1524
1661
 
1525
- ### `GET /api/ws`
1526
- Documentation placeholder for WebSocket access.
1527
-
1528
- **Auth:** browser cookie
1529
-
1530
- **Response:** plain text with HTTP `426 Upgrade Required`
1531
-
1532
- Example body:
1533
- ```text
1534
- WebSocket endpoint available at ws://HOST/ws. This route is a documentation placeholder and does not upgrade connections.
1535
- ```
1536
-
1537
- ### `GET /ws`
1538
- Live WebSocket endpoint exposed by the custom server.
1539
-
1540
- **Auth:** same browser session as the dashboard
1662
+ ### Terminal WebSocket
1541
1663
 
1542
- **Protocol notes:**
1543
- - Hosted on the main HTTP port, not under `/api/`
1544
- - Accepts JSON messages such as `chat.send` and `chat.history`
1545
- - Intended for the dashboard client rather than generic REST consumers
1664
+ Not an `/api/` route and not on the dashboard port. `terminal-ws-server.js`
1665
+ listens on **`ws://127.0.0.1:3741`** (`TERMINAL_WS_PORT`), bound to the loopback
1666
+ interface only, and speaks the PTY protocol described in
1667
+ [CONTAINER-TERMINAL.md](../CONTAINER-TERMINAL.md).
1546
1668
 
1547
1669
  ---
1548
1670
 
1671
+
1549
1672
  ## Plugins
1550
1673
 
1551
1674
  ### `GET /api/plugins`
@@ -1707,7 +1830,7 @@ Chat with the built-in AI assistant (PULSE).
1707
1830
  {
1708
1831
  "message": "What can I do on the Agents page?",
1709
1832
  "page": "/agents",
1710
- "model": "deepseek/deepseek-v4-flash",
1833
+ "model": "deepseek/deepseek-flash",
1711
1834
  "history": []
1712
1835
  }
1713
1836
  ```
@@ -1716,7 +1839,7 @@ Chat with the built-in AI assistant (PULSE).
1716
1839
  |-----------|-------------------------|----------|--------------------------------------------------|
1717
1840
  | `message` | string | yes | The user's question |
1718
1841
  | `page` | string | no | Current page path (used for contextual prompts) |
1719
- | `model` | string | no | AI model to use (e.g. `deepseek/deepseek-v4-flash`) |
1842
+ | `model` | string | no | AI model to use (e.g. `deepseek/deepseek-flash`) |
1720
1843
  | `history` | `{role, content}[]` | no | Recent conversation history (up to 10 messages) |
1721
1844
 
1722
1845
  **Response:** `text/event-stream` (Server-Sent Events)
@@ -1745,11 +1868,19 @@ data: [DONE]
1745
1868
  ### `GET /api/credentials`
1746
1869
  List all stored credential profiles and the provider registry.
1747
1870
 
1871
+ **Auth:** browser cookie or bearer token
1872
+
1748
1873
  **Response:** `{ profiles: CredentialProfile[], providers: CredentialProviderDef[] }`
1749
1874
 
1875
+ Secrets are never returned here — each profile carries only `secretMasked`.
1876
+
1877
+ Each entry in a provider's `fields` may carry `docsUrl` and `docsLabel`, pointing at the page where that specific value is created. The provider-level `docsUrl` is the general one shown on the card; the field-level pair is what the add/edit form renders next to the input it is asking you to fill.
1878
+
1750
1879
  ### `POST /api/credentials`
1751
1880
  Create a new credential profile.
1752
1881
 
1882
+ **Auth:** browser cookie or bearer token
1883
+
1753
1884
  **Body:** `{ providerId: string, label: string, secret: Record<string, string> }`
1754
1885
 
1755
1886
  **Response:** `{ id: string }` (201)
@@ -1757,30 +1888,77 @@ Create a new credential profile.
1757
1888
  ### `GET /api/credentials/[id]`
1758
1889
  Get a single credential profile (no secret returned).
1759
1890
 
1891
+ **Auth:** browser cookie or bearer token
1892
+
1760
1893
  ### `PATCH /api/credentials/[id]`
1761
1894
  Update label and/or secret of a credential.
1762
1895
 
1896
+ **Auth:** browser cookie or bearer token
1897
+
1763
1898
  **Body:** `{ label: string, secret: Record<string, string> }`
1764
1899
 
1900
+ Secret fields are **merged** onto the stored payload, not substituted for it:
1901
+ only the fields carrying a non-empty value are written, and every blank field
1902
+ keeps whatever is already stored. This matters for the one two-field provider,
1903
+ Trello, where rotating the `token` alone must leave `apiKey` intact — both are
1904
+ required by the REST delivery. An empty `secret` (every field blank) therefore
1905
+ updates the label only.
1906
+
1907
+ When the merge changes the stored payload, `last_synced_at` is reset to `NULL`:
1908
+ the agents still hold the previous value, so the profile reads "synced never"
1909
+ until it is delivered again, and `GET /api/credentials/detect` reports those
1910
+ agents as holding an unrecognised credential for that provider.
1911
+
1765
1912
  ### `DELETE /api/credentials/[id]`
1766
1913
  Delete a credential and its secret permanently.
1767
1914
 
1915
+ **Auth:** browser cookie or bearer token
1916
+
1768
1917
  ### `POST /api/credentials/[id]/sync`
1769
- Sync the credential to all agent containers (or a specified subset).
1918
+ Sync the credential to agent containers.
1770
1919
 
1771
- **Body (optional):** `{ containers?: string[] }` — if omitted or empty, syncs to all agent containers with `AGENT_ID` labels.
1920
+ **Auth:** browser cookie or bearer token
1921
+
1922
+ **Body (optional):** `{ containers?: string[] }` — if omitted, targets every agent container carrying an `AGENT_ID` label.
1772
1923
 
1773
1924
  **Response:** `{ results: { container: string, status: 'ok'|'error', error?: string }[] }`
1774
1925
 
1775
- Before syncing, **all agent containers are cleaned first** (env files, CLI auth, TOOLS.md integration markers removed).
1776
- Only the selected containers receive the new credential and TOOLS.md section.
1777
- This ensures that deselecting an agent or deleting a profile removes the credential from all agents.
1926
+ Each **targeted** container is first cleaned of this provider only — other
1927
+ providers on the same container are left alone — and then receives the new
1928
+ credential and its TOOLS.md row.
1929
+
1930
+ **Containers outside `containers` are not touched.** Syncing is therefore not a
1931
+ way to revoke: dropping an agent from the list leaves whatever it already holds
1932
+ in place. Use `DELETE` below to remove a credential from a container.
1933
+
1934
+ ### `DELETE /api/credentials/[id]/sync`
1935
+ Remove this credential from a single container ("de-sync").
1936
+
1937
+ **Auth:** browser cookie or bearer token
1938
+
1939
+ **Body:** `{ container: string }` — required.
1940
+
1941
+ **Response:** `{ ok: true }`
1942
+
1943
+ Deletes the provider's CLI auth inside that container and drops its TOOLS.md
1944
+ row. `last_synced_at` is deliberately left untouched — it records the last
1945
+ delivery, and a removal is not one — and the audit entry is recorded as
1946
+ `desync`. The profile stays `active`, because de-sync targets one container and
1947
+ the credential may still be delivered to others.
1778
1948
 
1779
1949
  ### `GET /api/credentials/detect`
1780
1950
  Live-detect which credentials are actually present inside agent containers.
1781
1951
 
1952
+ **Auth:** browser cookie or bearer token
1953
+
1782
1954
  **Query params (optional):** `?agent=prometheus` — filter to a single container.
1783
1955
 
1956
+ Costly: one `docker exec` per container, measured at ~6.5s each. Almost all of
1957
+ that is the three CLI calls reaching the network from inside the container, so
1958
+ the figure is largely platform-independent — the `docker exec` itself is ~90ms,
1959
+ and the local file reads another ~100ms. Callers should treat it as a background
1960
+ refresh, not something to block a page render on.
1961
+
1784
1962
  **Response:**
1785
1963
  ```json
1786
1964
  {
@@ -1802,34 +1980,119 @@ Live-detect which credentials are actually present inside agent containers.
1802
1980
  "supabaseCliInstalled": true,
1803
1981
  "trelloLoggedIn": false,
1804
1982
  "trelloCliInstalled": true,
1983
+ "notionLoggedIn": false,
1805
1984
  "toolsMdHasMarkers": true,
1806
1985
  "matchedProfiles": [
1807
1986
  { "profileId": "cred_xxx", "providerId": "github-pat", "label": "GitHub" }
1808
- ]
1987
+ ],
1988
+ "installedProviders": ["github-pat"]
1809
1989
  }
1810
1990
  ]
1811
1991
  }
1812
1992
  ```
1813
1993
 
1814
- Detection runs `docker exec` inside each container:
1815
- - `gh auth status` for GitHub (token read from `~/.config/gh/hosts.yml`)
1816
- - `vercel whoami` for Vercel (token read from `~/.local/share/com.vercel.cli/auth.json`)
1817
- - `supabase projects list --output json` for Supabase (token read from `~/.supabase/access-token`)
1818
- - `test -f ~/.trello-cli/default/config.json` for Trello
1819
- - TOOLS.md markers at `/root/.openclaw/workspace/TOOLS.md`
1994
+ An agent whose probe failed is returned in its place as
1995
+ `{ "container", "agentId", "error" }` — none of the fields above are present, so
1996
+ consumers must check `error` first.
1997
+
1998
+ Each container is read with a **single** `docker exec` running one script that
1999
+ emits NUL-delimited key/value pairs; containers are probed concurrently. The
2000
+ script reads the credential files directly and the parsing happens in
2001
+ JavaScript. Three CLI calls run last and only feed display fields — `githubUser`,
2002
+ `vercelUser`, `vercelTeam`, and the `githubLoggedIn` / `vercelLoggedIn` /
2003
+ `supabaseLoggedIn` flags, along with `supabaseLinked` and `supabaseProjectRef`,
2004
+ which are read only when `supabaseLoggedIn` is true. `supabaseLoggedIn` follows
2005
+ the **exit status** of `supabase projects list`, not the text it prints, so a
2006
+ failure whose wording is unfamiliar still reads as not logged in.
2007
+ `trelloLoggedIn` and `notionLoggedIn` come from the config files instead, and so
2008
+ survive a probe cut short. The three calls reach the network from inside the
2009
+ container, so a completion sentinel is emitted before them: a probe cut short
2010
+ during them still yields valid token matching, with those display fields empty.
2011
+
2012
+ Credential files read: `~/.config/gh/hosts.yml`,
2013
+ `~/.local/share/com.vercel.cli/auth.json` and `config.json`,
2014
+ `~/.supabase/access-token` and `/data/supabase/config.toml`,
2015
+ `~/.config/trello/config.json`, `~/.config/notion/config.json`, plus TOOLS.md
2016
+ markers at `/root/.openclaw/workspace/TOOLS.md`.
1820
2017
 
1821
2018
  `matchedProfiles` is determined by SHA256-hashing the token installed in the container
1822
2019
  and comparing it against the hashed token of each stored profile in the vault.
1823
2020
  Only exact token matches produce a match — no heuristics, no broad profile-level guesses.
1824
2021
 
2022
+ `installedProviders` lists every provider holding a token, matched or not. A
2023
+ provider present there but absent from `matchedProfiles` holds a credential no
2024
+ stored profile accounts for — an edited-but-not-resynced credential, one
2025
+ installed by hand, or the remains of a deleted profile.
2026
+
1825
2027
  ### `POST /api/credentials/[id]/reveal`
1826
- Return the raw secret payload. Every call is logged in the audit trail.
2028
+ Return the raw secret payload. Every call is written to `credential_audit_log`, which no route or view reads.
1827
2029
  Use sparingly.
1828
2030
 
2031
+ **Auth:** browser cookie or bearer token
2032
+
2033
+ Secrets are stored **unencrypted** in `credentials.db` — `credential_secrets.payload`
2034
+ holds the raw JSON. Anything that can read that file, or reach this endpoint with a
2035
+ valid session, has the secrets in the clear.
2036
+
1829
2037
  **Response:** `{ secret: Record<string, string> }`
1830
2038
 
1831
2039
  ---
1832
2040
 
2041
+ ## Diagnostics
2042
+
2043
+ ### `GET /api/debug`
2044
+ Whether the three auth-related environment variables are set. Booleans only —
2045
+ no value, masked or otherwise, is returned.
2046
+
2047
+ **Auth:** browser cookie or bearer token
2048
+
2049
+ **Response:** `{ "hasPassword": true, "hasJwtSecret": true, "hasToken": false }`
2050
+
2051
+ ### `GET /api/envcheck`
2052
+ The process environment, with `REV4A_PASSWORD`, `REV4A_TOKEN`,
2053
+ `REV4A_JWT_SECRET` and `PROVIDER_DEEPSEEK_API_KEY` redacted. Every other
2054
+ variable is returned **in the clear**, so treat the response as sensitive.
2055
+
2056
+ **Auth:** browser cookie or bearer token
2057
+
2058
+ ### `GET /api/provider-usage`
2059
+ Usage and quota state per provider, including live OpenRouter figures.
2060
+
2061
+ **Auth:** browser cookie or bearer token
2062
+
2063
+ **Response:** `{ "providers": [...], "openrouterLive": {…}, "quotas": {…} }`
2064
+
2065
+ ### `GET /api/update-check`
2066
+ Installed version against the latest published one.
2067
+
2068
+ **Auth:** browser cookie or bearer token
2069
+
2070
+ **Query params:** `check=1` queries the registry; without it the answer comes
2071
+ from local state only.
2072
+
2073
+ **Response:** `{ "installed": "1.1.15", "latest": "1.1.16", "updateAvailable": true, "package": "@flame0510/project-aether" }`
2074
+
2075
+ ### `POST /api/update-check`
2076
+ Run `rev4a update` as a detached background process. The dashboard restarts
2077
+ itself as part of the update, so the connection drops.
2078
+
2079
+ **Auth:** browser cookie or bearer token
2080
+
2081
+ ### `GET /api/alerts/smoke`
2082
+ Send a test alert, to verify the Telegram alerting path end to end.
2083
+
2084
+ **Auth:** browser cookie or bearer token
2085
+
2086
+ **Query params:** `stale=0` skips the staleness check.
2087
+
2088
+ **Response:** `{ "ok": true, "stale": true, "result": {…}, "dryRun": false }` —
2089
+ `dryRun` is `true` when `REV4A_TELEGRAM_BOT_TOKEN` or `REV4A_TELEGRAM_CHAT_ID`
2090
+ is unset, meaning nothing was actually sent.
2091
+
2092
+ **Errors:** `403` when smoke alerts are disabled.
2093
+
2094
+ ---
2095
+
1833
2096
  ## Error Format
1834
2097
 
1835
2098
  All errors return JSON: