@chrysb/alphaclaw 0.4.6-beta.3 → 0.4.6-beta.5

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 (39) hide show
  1. package/lib/public/js/app.js +158 -1073
  2. package/lib/public/js/components/doctor/index.js +1 -2
  3. package/lib/public/js/components/general/index.js +155 -0
  4. package/lib/public/js/components/general/use-general-tab.js +233 -0
  5. package/lib/public/js/components/models-tab/index.js +286 -0
  6. package/lib/public/js/components/models-tab/provider-auth-card.js +369 -0
  7. package/lib/public/js/components/models-tab/use-models.js +262 -0
  8. package/lib/public/js/components/routes/browse-route.js +35 -0
  9. package/lib/public/js/components/routes/doctor-route.js +21 -0
  10. package/lib/public/js/components/routes/envars-route.js +11 -0
  11. package/lib/public/js/components/routes/general-route.js +45 -0
  12. package/lib/public/js/components/routes/index.js +11 -0
  13. package/lib/public/js/components/routes/models-route.js +11 -0
  14. package/lib/public/js/components/routes/providers-route.js +11 -0
  15. package/lib/public/js/components/routes/route-redirect.js +10 -0
  16. package/lib/public/js/components/routes/telegram-route.js +11 -0
  17. package/lib/public/js/components/routes/usage-route.js +15 -0
  18. package/lib/public/js/components/routes/watchdog-route.js +32 -0
  19. package/lib/public/js/components/routes/webhooks-route.js +43 -0
  20. package/lib/public/js/components/sidebar.js +2 -3
  21. package/lib/public/js/components/usage-tab/constants.js +1 -1
  22. package/lib/public/js/components/usage-tab/overview-section.js +124 -50
  23. package/lib/public/js/components/usage-tab/use-usage-tab.js +42 -11
  24. package/lib/public/js/hooks/use-app-shell-controller.js +230 -0
  25. package/lib/public/js/hooks/use-app-shell-ui.js +112 -0
  26. package/lib/public/js/hooks/use-browse-navigation.js +193 -0
  27. package/lib/public/js/hooks/use-hash-location.js +32 -0
  28. package/lib/public/js/lib/api.js +35 -0
  29. package/lib/public/js/lib/app-navigation.js +39 -0
  30. package/lib/public/js/lib/browse-restart-policy.js +28 -0
  31. package/lib/public/js/lib/browse-route.js +57 -0
  32. package/lib/public/js/lib/format.js +12 -0
  33. package/lib/server/auth-profiles.js +223 -52
  34. package/lib/server/doctor/prompt.js +4 -1
  35. package/lib/server/gateway.js +29 -9
  36. package/lib/server/routes/models.js +170 -2
  37. package/lib/server/watchdog.js +14 -1
  38. package/lib/server.js +1 -0
  39. package/package.json +1 -1
@@ -1,6 +1,27 @@
1
1
  const { kFallbackOnboardingModels } = require("../constants");
2
2
 
3
- const registerModelRoutes = ({ app, shellCmd, gatewayEnv, parseJsonFromNoisyOutput, normalizeOnboardingModels }) => {
3
+ const runModelsGitSync = async (shellCmd) => {
4
+ if (typeof shellCmd !== "function") return null;
5
+ try {
6
+ await shellCmd('alphaclaw git-sync -m "models: update config" -f "openclaw.json"', {
7
+ timeout: 30000,
8
+ });
9
+ return null;
10
+ } catch (err) {
11
+ return err?.message || "alphaclaw git-sync failed";
12
+ }
13
+ };
14
+
15
+ const registerModelRoutes = ({
16
+ app,
17
+ shellCmd,
18
+ gatewayEnv,
19
+ parseJsonFromNoisyOutput,
20
+ normalizeOnboardingModels,
21
+ authProfiles,
22
+ }) => {
23
+ // ── Existing CLI-backed catalog/status routes ──
24
+
4
25
  app.get("/api/models", async (req, res) => {
5
26
  try {
6
27
  const output = await shellCmd("openclaw models list --all --json", {
@@ -60,7 +81,154 @@ const registerModelRoutes = ({ app, shellCmd, gatewayEnv, parseJsonFromNoisyOutp
60
81
  });
61
82
  res.json({ ok: true });
62
83
  } catch (err) {
63
- res.status(400).json({ ok: false, error: err.message || "Failed to set model" });
84
+ res
85
+ .status(400)
86
+ .json({ ok: false, error: err.message || "Failed to set model" });
87
+ }
88
+ });
89
+
90
+ // ── Model config (direct JSON) ──
91
+
92
+ app.get("/api/models/config", (req, res) => {
93
+ try {
94
+ const { primary, configuredModels } = authProfiles.getModelConfig();
95
+ const agentId = req.query.agentId || undefined;
96
+ const profiles = authProfiles.listProfiles(agentId);
97
+ const store = authProfiles.loadAuthStore(agentId);
98
+ res.json({
99
+ ok: true,
100
+ primary,
101
+ configuredModels,
102
+ authProfiles: profiles,
103
+ authOrder: store.order || {},
104
+ });
105
+ } catch (err) {
106
+ res
107
+ .status(500)
108
+ .json({ ok: false, error: err.message || "Failed to read config" });
109
+ }
110
+ });
111
+
112
+ app.put("/api/models/config", async (req, res) => {
113
+ const { primary, configuredModels, profiles, authOrder } = req.body || {};
114
+ const agentId = req.query.agentId || undefined;
115
+ if (primary !== undefined && (typeof primary !== "string" || !primary.includes("/"))) {
116
+ return res
117
+ .status(400)
118
+ .json({ ok: false, error: "Invalid primary model key" });
119
+ }
120
+ if (
121
+ configuredModels !== undefined &&
122
+ (typeof configuredModels !== "object" || configuredModels === null)
123
+ ) {
124
+ return res
125
+ .status(400)
126
+ .json({ ok: false, error: "Invalid configuredModels" });
127
+ }
128
+ try {
129
+ authProfiles.setModelConfig({ primary, configuredModels });
130
+
131
+ if (Array.isArray(profiles)) {
132
+ for (const { id: profileId, ...credential } of profiles) {
133
+ if (profileId && credential.type && credential.provider) {
134
+ authProfiles.upsertProfile(profileId, credential, agentId);
135
+ }
136
+ }
137
+ }
138
+
139
+ if (authOrder && typeof authOrder === "object") {
140
+ for (const [provider, order] of Object.entries(authOrder)) {
141
+ if (Array.isArray(order)) {
142
+ authProfiles.setAuthOrder(provider, order, agentId);
143
+ }
144
+ }
145
+ }
146
+
147
+ // `auth-profiles.json` is the durable source of truth. Re-sync
148
+ // `openclaw.json.auth.profiles` on save so model re-adds restore refs.
149
+ authProfiles.syncConfigAuthReferencesForAgent(agentId);
150
+
151
+ const syncWarning = await runModelsGitSync(shellCmd);
152
+ res.json({
153
+ ok: true,
154
+ ...(syncWarning ? { syncWarning } : {}),
155
+ });
156
+ } catch (err) {
157
+ res
158
+ .status(500)
159
+ .json({ ok: false, error: err.message || "Failed to save config" });
160
+ }
161
+ });
162
+
163
+ // ── Auth profiles (direct JSON) ──
164
+
165
+ app.get("/api/models/auth", (req, res) => {
166
+ try {
167
+ const agentId = req.query.agentId || undefined;
168
+ const profiles = authProfiles.listProfiles(agentId);
169
+ const store = authProfiles.loadAuthStore(agentId);
170
+ res.json({ ok: true, profiles, order: store.order || {} });
171
+ } catch (err) {
172
+ res
173
+ .status(500)
174
+ .json({
175
+ ok: false,
176
+ error: err.message || "Failed to read auth profiles",
177
+ });
178
+ }
179
+ });
180
+
181
+ app.put("/api/models/auth/:profileId", (req, res) => {
182
+ const { profileId } = req.params;
183
+ const credential = req.body;
184
+ if (
185
+ !profileId ||
186
+ !credential?.type ||
187
+ !credential?.provider
188
+ ) {
189
+ return res
190
+ .status(400)
191
+ .json({ ok: false, error: "Missing profileId, type, or provider" });
192
+ }
193
+ const validTypes = new Set(["api_key", "token", "oauth"]);
194
+ if (!validTypes.has(credential.type)) {
195
+ return res.status(400).json({
196
+ ok: false,
197
+ error: `Invalid credential type: ${credential.type}`,
198
+ });
199
+ }
200
+ try {
201
+ const agentId = req.query.agentId || undefined;
202
+ authProfiles.upsertProfile(profileId, credential, agentId);
203
+ res.json({ ok: true });
204
+ } catch (err) {
205
+ res
206
+ .status(500)
207
+ .json({
208
+ ok: false,
209
+ error: err.message || "Failed to save auth profile",
210
+ });
211
+ }
212
+ });
213
+
214
+ app.delete("/api/models/auth/:profileId", (req, res) => {
215
+ const { profileId } = req.params;
216
+ if (!profileId) {
217
+ return res
218
+ .status(400)
219
+ .json({ ok: false, error: "Missing profileId" });
220
+ }
221
+ try {
222
+ const agentId = req.query.agentId || undefined;
223
+ const removed = authProfiles.removeProfile(profileId, agentId);
224
+ res.json({ ok: true, removed });
225
+ } catch (err) {
226
+ res
227
+ .status(500)
228
+ .json({
229
+ ok: false,
230
+ error: err.message || "Failed to remove auth profile",
231
+ });
64
232
  }
65
233
  });
66
234
  };
@@ -364,14 +364,27 @@ const createWatchdog = ({
364
364
  }
365
365
  if (parsed.ok) {
366
366
  const wasUnhealthy = state.health !== "healthy";
367
+ const recoveredFromCrashLoop = state.lifecycle === "crash_loop";
367
368
  state.startupConsecutiveHealthFailures = 0;
368
369
  clearDegradedHealthCheckTimer();
369
370
  clearExpectedRestartWindow();
370
371
  state.health = "healthy";
371
- if (state.lifecycle !== "crash_loop") state.lifecycle = "running";
372
+ state.lifecycle = "running";
372
373
  if (!state.uptimeStartedAt || wasUnhealthy) state.uptimeStartedAt = Date.now();
373
374
  state.repairAttempts = 0;
374
375
  state.crashRecoveryActive = false;
376
+ if (recoveredFromCrashLoop) {
377
+ logEvent(
378
+ "recovery",
379
+ source,
380
+ "ok",
381
+ {
382
+ previousLifecycle: "crash_loop",
383
+ health: "healthy",
384
+ },
385
+ correlationId,
386
+ );
387
+ }
375
388
  if (state.pendingRecoveryNoticeSource) {
376
389
  const recoverySource = state.pendingRecoveryNoticeSource;
377
390
  state.pendingRecoveryNoticeSource = "";
package/lib/server.js CHANGED
@@ -203,6 +203,7 @@ registerModelRoutes({
203
203
  gatewayEnv,
204
204
  parseJsonFromNoisyOutput,
205
205
  normalizeOnboardingModels,
206
+ authProfiles,
206
207
  });
207
208
  registerOnboardingRoutes({
208
209
  app,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.4.6-beta.3",
3
+ "version": "0.4.6-beta.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },