@chrysb/alphaclaw 0.9.16 → 0.9.17

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 (34) hide show
  1. package/lib/public/css/tailwind.generated.css +1 -1
  2. package/lib/public/dist/app.bundle.js +1519 -1459
  3. package/lib/public/js/components/agents-tab/agent-overview/model-card.js +59 -7
  4. package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +124 -0
  5. package/lib/public/js/components/envars.js +1 -1
  6. package/lib/public/js/components/row-accessory-select.js +52 -0
  7. package/lib/public/js/lib/api.js +7 -0
  8. package/lib/public/js/lib/model-catalog.js +6 -0
  9. package/lib/public/js/lib/model-config.js +12 -7
  10. package/lib/public/js/lib/thinking-levels.js +37 -0
  11. package/lib/server/agents/agents.js +33 -7
  12. package/lib/server/agents/channels.js +4 -2
  13. package/lib/server/chat-ws.js +4 -1
  14. package/lib/server/constants.js +25 -0
  15. package/lib/server/cost-utils.js +2 -0
  16. package/lib/server/db/auth/index.js +147 -0
  17. package/lib/server/db/auth/schema.js +17 -0
  18. package/lib/server/gateway.js +158 -19
  19. package/lib/server/helpers.js +1 -3
  20. package/lib/server/init/register-server-routes.js +37 -18
  21. package/lib/server/init/runtime-init.js +4 -0
  22. package/lib/server/init/server-lifecycle.js +1 -24
  23. package/lib/server/login-throttle.js +242 -60
  24. package/lib/server/model-catalog-bootstrap.json +5 -0
  25. package/lib/server/onboarding/index.js +2 -2
  26. package/lib/server/openclaw-thinking.js +103 -0
  27. package/lib/server/openclaw-version.js +1 -1
  28. package/lib/server/routes/agents.js +10 -3
  29. package/lib/server/routes/models.js +35 -1
  30. package/lib/server/routes/onboarding.js +2 -2
  31. package/lib/server/routes/system.js +2 -2
  32. package/lib/server/usage-tracker-config.js +52 -1
  33. package/lib/server.js +26 -22
  34. package/package.json +2 -2
@@ -0,0 +1,147 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { DatabaseSync } = require("node:sqlite");
4
+ const { createSchema } = require("./schema");
5
+
6
+ let db = null;
7
+
8
+ const ensureDb = () => {
9
+ if (!db) throw new Error("Auth DB not initialized");
10
+ return db;
11
+ };
12
+
13
+ const closeAuthDb = () => {
14
+ if (!db) return;
15
+ const database = db;
16
+ db = null;
17
+ database.close();
18
+ };
19
+
20
+ const initAuthDb = ({ rootDir }) => {
21
+ closeAuthDb();
22
+ const dbDir = path.join(rootDir, "db");
23
+ fs.mkdirSync(dbDir, { recursive: true });
24
+ const dbPath = path.join(dbDir, "auth.db");
25
+ db = new DatabaseSync(dbPath);
26
+ db.exec("PRAGMA busy_timeout = 5000");
27
+ createSchema(db);
28
+ return { path: dbPath };
29
+ };
30
+
31
+ const toStateModel = (row) => {
32
+ if (!row) return null;
33
+ return {
34
+ attempts: Number(row.attempts || 0),
35
+ windowStart: Number(row.window_start || 0),
36
+ lockUntil: Number(row.lock_until || 0),
37
+ failStreak: Number(row.fail_streak || 0),
38
+ lastSeenAt: Number(row.last_seen_at || 0),
39
+ };
40
+ };
41
+
42
+ const createLoginThrottleStore = () => ({
43
+ get: (stateKey) => {
44
+ const row = ensureDb()
45
+ .prepare(
46
+ `
47
+ SELECT
48
+ attempts,
49
+ window_start,
50
+ lock_until,
51
+ fail_streak,
52
+ last_seen_at
53
+ FROM login_throttle_states
54
+ WHERE state_key = $state_key
55
+ LIMIT 1
56
+ `,
57
+ )
58
+ .get({ $state_key: String(stateKey || "") });
59
+ return toStateModel(row);
60
+ },
61
+
62
+ set: (stateKey, state) => {
63
+ ensureDb()
64
+ .prepare(
65
+ `
66
+ INSERT INTO login_throttle_states (
67
+ state_key,
68
+ attempts,
69
+ window_start,
70
+ lock_until,
71
+ fail_streak,
72
+ last_seen_at
73
+ ) VALUES (
74
+ $state_key,
75
+ $attempts,
76
+ $window_start,
77
+ $lock_until,
78
+ $fail_streak,
79
+ $last_seen_at
80
+ )
81
+ ON CONFLICT(state_key) DO UPDATE SET
82
+ attempts = excluded.attempts,
83
+ window_start = excluded.window_start,
84
+ lock_until = excluded.lock_until,
85
+ fail_streak = excluded.fail_streak,
86
+ last_seen_at = excluded.last_seen_at
87
+ `,
88
+ )
89
+ .run({
90
+ $state_key: String(stateKey || ""),
91
+ $attempts: Number(state?.attempts || 0),
92
+ $window_start: Number(state?.windowStart || 0),
93
+ $lock_until: Number(state?.lockUntil || 0),
94
+ $fail_streak: Number(state?.failStreak || 0),
95
+ $last_seen_at: Number(state?.lastSeenAt || 0),
96
+ });
97
+ },
98
+
99
+ delete: (stateKey) => {
100
+ ensureDb()
101
+ .prepare(
102
+ `
103
+ DELETE FROM login_throttle_states
104
+ WHERE state_key = $state_key
105
+ `,
106
+ )
107
+ .run({ $state_key: String(stateKey || "") });
108
+ },
109
+
110
+ entries: () =>
111
+ ensureDb()
112
+ .prepare(
113
+ `
114
+ SELECT
115
+ state_key,
116
+ attempts,
117
+ window_start,
118
+ lock_until,
119
+ fail_streak,
120
+ last_seen_at
121
+ FROM login_throttle_states
122
+ `,
123
+ )
124
+ .all()
125
+ .map((row) => [String(row.state_key || ""), toStateModel(row)]),
126
+
127
+ runExclusive: (callback) => {
128
+ const database = ensureDb();
129
+ database.exec("BEGIN IMMEDIATE");
130
+ try {
131
+ const result = callback();
132
+ database.exec("COMMIT");
133
+ return result;
134
+ } catch (err) {
135
+ try {
136
+ database.exec("ROLLBACK");
137
+ } catch {}
138
+ throw err;
139
+ }
140
+ },
141
+ });
142
+
143
+ module.exports = {
144
+ initAuthDb,
145
+ closeAuthDb,
146
+ createLoginThrottleStore,
147
+ };
@@ -0,0 +1,17 @@
1
+ const createSchema = (db) => {
2
+ db.exec(`
3
+ CREATE TABLE IF NOT EXISTS login_throttle_states (
4
+ state_key TEXT PRIMARY KEY,
5
+ attempts INTEGER NOT NULL DEFAULT 0,
6
+ window_start INTEGER NOT NULL,
7
+ lock_until INTEGER NOT NULL DEFAULT 0,
8
+ fail_streak INTEGER NOT NULL DEFAULT 0,
9
+ last_seen_at INTEGER NOT NULL
10
+ );
11
+
12
+ CREATE INDEX IF NOT EXISTS idx_login_throttle_states_last_seen
13
+ ON login_throttle_states(last_seen_at);
14
+ `);
15
+ };
16
+
17
+ module.exports = { createSchema };
@@ -18,6 +18,10 @@ let gatewayExitHandler = null;
18
18
  let gatewayLaunchHandler = null;
19
19
  const kGatewayStderrTailLines = 50;
20
20
  const kPluginRuntimeDepsPreflightTimeoutMs = 120 * 1000;
21
+ const kGatewayShortCmdTimeoutMs = 15 * 1000;
22
+ const kGatewayLifecycleCmdTimeoutMs = 90 * 1000;
23
+ const kGatewayRestartReadyTimeoutMs = 120 * 1000;
24
+ const kGatewayRestartReadyPollMs = 500;
21
25
  let gatewayStderrTail = [];
22
26
  const expectedExitPids = new Set();
23
27
 
@@ -225,27 +229,122 @@ const isGatewayRunning = () =>
225
229
  });
226
230
  });
227
231
 
228
- const runGatewayCmd = (cmd) => {
229
- console.log(`[alphaclaw] Running: openclaw gateway ${cmd}`);
232
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
233
+
234
+ const waitForGatewayReady = async ({
235
+ timeoutMs = kGatewayRestartReadyTimeoutMs,
236
+ } = {}) => {
237
+ const startedAt = Date.now();
238
+ while (Date.now() - startedAt < timeoutMs) {
239
+ if (await isGatewayRunning()) return true;
240
+ await sleep(kGatewayRestartReadyPollMs);
241
+ }
242
+ return false;
243
+ };
244
+
245
+ const logGatewayCmdOutput = (cmd, e) => {
246
+ if (e?.stdout?.trim()) {
247
+ console.log(`[alphaclaw] gateway ${cmd} stdout: ${e.stdout.trim()}`);
248
+ }
249
+ if (e?.stderr?.trim()) {
250
+ console.log(`[alphaclaw] gateway ${cmd} stderr: ${e.stderr.trim()}`);
251
+ }
252
+ if (!e?.stdout?.trim() && !e?.stderr?.trim()) {
253
+ console.log(`[alphaclaw] gateway ${cmd} error: ${e.message}`);
254
+ }
255
+ if (e?.status !== undefined && e?.status !== null) {
256
+ console.log(`[alphaclaw] gateway ${cmd} exit code: ${e.status}`);
257
+ }
258
+ };
259
+
260
+ const runGatewayShortCmd = (cmd) => {
230
261
  try {
231
- if (cmd === "--force" || cmd === "restart") {
232
- prepareOpenclawChannelPlugins();
233
- }
234
262
  const out = execSync(`openclaw gateway ${cmd}`, {
235
263
  env: gatewayEnv(),
236
- timeout: 15000,
264
+ timeout: kGatewayShortCmdTimeoutMs,
237
265
  encoding: "utf8",
238
266
  });
239
267
  if (out.trim()) console.log(`[alphaclaw] ${out.trim()}`);
240
268
  } catch (e) {
241
- if (e.stdout?.trim())
242
- console.log(`[alphaclaw] gateway ${cmd} stdout: ${e.stdout.trim()}`);
243
- if (e.stderr?.trim())
244
- console.log(`[alphaclaw] gateway ${cmd} stderr: ${e.stderr.trim()}`);
245
- if (!e.stdout?.trim() && !e.stderr?.trim())
246
- console.log(`[alphaclaw] gateway ${cmd} error: ${e.message}`);
247
- console.log(`[alphaclaw] gateway ${cmd} exit code: ${e.status}`);
269
+ logGatewayCmdOutput(cmd, e);
270
+ }
271
+ };
272
+
273
+ const runGatewayLifecycleRestart = () => {
274
+ console.log("[alphaclaw] Running: openclaw gateway restart");
275
+ try {
276
+ const out = execSync("openclaw gateway restart", {
277
+ env: gatewayEnv(),
278
+ timeout: kGatewayLifecycleCmdTimeoutMs,
279
+ encoding: "utf8",
280
+ });
281
+ if (out.trim()) console.log(`[alphaclaw] ${out.trim()}`);
282
+ return true;
283
+ } catch (e) {
284
+ logGatewayCmdOutput("restart", e);
285
+ return false;
286
+ }
287
+ };
288
+
289
+ const hasActiveManagedGatewayChild = () =>
290
+ !!(
291
+ gatewayChild &&
292
+ gatewayChild.exitCode === null &&
293
+ !gatewayChild.killed
294
+ );
295
+
296
+ const runGatewayRestartCmd = async (cmd) => {
297
+ prepareOpenclawChannelPlugins();
298
+ const startedAt = Date.now();
299
+ const child = spawn("openclaw", ["gateway", cmd], {
300
+ env: gatewayEnv(),
301
+ stdio: ["ignore", "pipe", "pipe"],
302
+ });
303
+ child.stdout.on("data", (d) => process.stdout.write(`[gateway] ${d}`));
304
+ child.stderr.on("data", (d) => {
305
+ appendStderrTail(d);
306
+ process.stderr.write(`[gateway] ${d}`);
307
+ });
308
+ child.on("exit", (code, signal) => {
309
+ console.log(
310
+ `[alphaclaw] gateway ${cmd} supervisor exited: code=${code ?? "null"}${signal ? ` signal=${signal}` : ""}`,
311
+ );
312
+ });
313
+
314
+ const ready = await waitForGatewayReady();
315
+ if (ready) {
316
+ console.log(
317
+ `[alphaclaw] Gateway ${cmd} ready (${Date.now() - startedAt}ms); leaving supervisor running`,
318
+ );
319
+ gatewayChild = null;
320
+ await notifyGatewayLaunch();
321
+ return;
322
+ }
323
+
324
+ console.warn(
325
+ `[alphaclaw] Gateway ${cmd} did not become ready within ${kGatewayRestartReadyTimeoutMs}ms; stopping`,
326
+ );
327
+ try {
328
+ child.kill("SIGTERM");
329
+ } catch {
330
+ // ignore
331
+ }
332
+ runGatewayShortCmd("stop");
333
+ };
334
+
335
+ const runGatewayColdStart = async () => {
336
+ stopManagedGatewayChild();
337
+ runGatewayShortCmd("stop");
338
+ await runGatewayRestartCmd("--force");
339
+ };
340
+
341
+ const runGatewayCmd = async (cmd) => {
342
+ console.log(`[alphaclaw] Running: openclaw gateway ${cmd}`);
343
+ if (cmd === "--force") {
344
+ await runGatewayRestartCmd("--force");
345
+ return;
248
346
  }
347
+ runGatewayShortCmd(cmd);
249
348
  };
250
349
 
251
350
  const launchGatewayProcess = () => {
@@ -322,6 +421,23 @@ const markManagedGatewayExitExpected = () => {
322
421
  return true;
323
422
  };
324
423
 
424
+ const notifyGatewayLaunch = async () => {
425
+ if (!gatewayLaunchHandler) return;
426
+ if (!(await isGatewayRunning())) return;
427
+ const pid =
428
+ gatewayChild &&
429
+ gatewayChild.exitCode === null &&
430
+ !gatewayChild.killed &&
431
+ gatewayChild.pid
432
+ ? gatewayChild.pid
433
+ : null;
434
+ try {
435
+ gatewayLaunchHandler({ startedAt: Date.now(), pid });
436
+ } catch (err) {
437
+ console.error(`[alphaclaw] Gateway launch handler error: ${err.message}`);
438
+ }
439
+ };
440
+
325
441
  const startGateway = async () => {
326
442
  if (!isOnboarded()) {
327
443
  console.log("[alphaclaw] Not onboarded yet — skipping gateway start");
@@ -329,22 +445,45 @@ const startGateway = async () => {
329
445
  }
330
446
  if (await isGatewayRunning()) {
331
447
  console.log("[alphaclaw] Gateway already running — skipping start");
448
+ await notifyGatewayLaunch();
332
449
  return;
333
450
  }
334
451
  console.log("[alphaclaw] Starting openclaw gateway...");
335
452
  launchGatewayProcess();
336
453
  };
337
454
 
338
- const restartGateway = (reloadEnv) => {
339
- reloadEnv();
455
+ const stopManagedGatewayChild = () => {
340
456
  markManagedGatewayExitExpected();
341
- runGatewayCmd("--force");
457
+ if (!gatewayChild || gatewayChild.exitCode !== null || gatewayChild.killed) {
458
+ return;
459
+ }
460
+ try {
461
+ gatewayChild.kill("SIGTERM");
462
+ } catch {
463
+ // ignore
464
+ }
465
+ gatewayChild = null;
342
466
  };
343
467
 
344
- const restartGatewayLight = (reloadEnv) => {
468
+ const restartGateway = async (reloadEnv) => {
345
469
  reloadEnv();
346
- markManagedGatewayExitExpected();
347
- runGatewayCmd("restart");
470
+ await runGatewayColdStart();
471
+ };
472
+
473
+ const restartGatewayLight = async (reloadEnv) => {
474
+ reloadEnv();
475
+ if (await isGatewayRunning()) {
476
+ if (runGatewayLifecycleRestart()) {
477
+ console.log("[alphaclaw] Gateway light restart complete");
478
+ return;
479
+ }
480
+ console.warn("[alphaclaw] Gateway light restart failed");
481
+ return;
482
+ }
483
+ if (!hasActiveManagedGatewayChild()) {
484
+ console.log("[alphaclaw] Gateway not running — starting managed process");
485
+ launchGatewayProcess();
486
+ }
348
487
  };
349
488
 
350
489
  const attachGatewaySignalHandlers = () => {
@@ -60,9 +60,7 @@ const isDebugEnabled = () =>
60
60
  isTruthyEnvFlag(process.env.DEBUG);
61
61
 
62
62
  const getClientKey = (req) =>
63
- normalizeIp(
64
- req.ip || req.headers["x-forwarded-for"] || req.socket?.remoteAddress || "",
65
- ) || "unknown";
63
+ normalizeIp(req.ip || req.socket?.remoteAddress || "") || "unknown";
66
64
 
67
65
  const resolveGithubRepoUrl = (repoInput) => {
68
66
  const cleaned = String(repoInput || "")
@@ -1,3 +1,4 @@
1
+ const { runOnboardedBootSequence } = require("../startup");
1
2
  const { registerAuthRoutes } = require("../routes/auth");
2
3
  const { registerPageRoutes } = require("../routes/pages");
3
4
  const { registerModelRoutes } = require("../routes/models");
@@ -43,6 +44,9 @@ const registerServerRoutes = ({
43
44
  ensureGatewayProxyConfig,
44
45
  getBaseUrl,
45
46
  startGateway,
47
+ ensureManagedExecDefaults,
48
+ ensureUsageTrackerPluginConfig,
49
+ resolveSetupUrl,
46
50
  syncChannelConfig,
47
51
  getChannelStatus,
48
52
  openclawVersionService,
@@ -105,24 +109,6 @@ const registerServerRoutes = ({
105
109
  writeEnvFile,
106
110
  reloadEnv,
107
111
  });
108
- registerOnboardingRoutes({
109
- app,
110
- fs,
111
- constants,
112
- shellCmd,
113
- gatewayEnv,
114
- readEnvFile,
115
- writeEnvFile,
116
- reloadEnv,
117
- isOnboarded,
118
- resolveGithubRepoUrl,
119
- resolveModelProvider,
120
- hasCodexOauthProfile: authProfiles.hasCodexOauthProfile,
121
- authProfiles,
122
- ensureGatewayProxyConfig,
123
- getBaseUrl,
124
- startGateway,
125
- });
126
112
  registerSystemRoutes({
127
113
  app,
128
114
  fs,
@@ -183,6 +169,38 @@ const registerServerRoutes = ({
183
169
  reloadEnv,
184
170
  restartRequiredState,
185
171
  });
172
+ const runOnboardedBoot = () =>
173
+ runOnboardedBootSequence({
174
+ ensureManagedExecDefaults,
175
+ ensureUsageTrackerPluginConfig,
176
+ doSyncPromptFiles,
177
+ reloadEnv,
178
+ syncChannelConfig,
179
+ readEnvFile,
180
+ ensureGatewayProxyConfig,
181
+ resolveSetupUrl,
182
+ startGateway,
183
+ watchdog,
184
+ gmailWatchService,
185
+ });
186
+ registerOnboardingRoutes({
187
+ app,
188
+ fs,
189
+ constants,
190
+ shellCmd,
191
+ gatewayEnv,
192
+ readEnvFile,
193
+ writeEnvFile,
194
+ reloadEnv,
195
+ isOnboarded,
196
+ resolveGithubRepoUrl,
197
+ resolveModelProvider,
198
+ hasCodexOauthProfile: authProfiles.hasCodexOauthProfile,
199
+ authProfiles,
200
+ ensureGatewayProxyConfig,
201
+ getBaseUrl,
202
+ runOnboardedBootSequence: runOnboardedBoot,
203
+ });
186
204
  registerTelegramRoutes({
187
205
  app,
188
206
  telegramApi,
@@ -266,6 +284,7 @@ const registerServerRoutes = ({
266
284
  requireAuth,
267
285
  isAuthorizedRequest,
268
286
  gmailWatchService,
287
+ runOnboardedBootSequence: runOnboardedBoot,
269
288
  };
270
289
  };
271
290
 
@@ -19,11 +19,15 @@ const initializeServerRuntime = ({
19
19
 
20
20
  const initializeServerDatabases = ({
21
21
  constants,
22
+ initAuthDb,
22
23
  initWebhooksDb,
23
24
  initWatchdogDb,
24
25
  initUsageDb,
25
26
  initDoctorDb,
26
27
  }) => {
28
+ initAuthDb({
29
+ rootDir: constants.kRootDir,
30
+ });
27
31
  initWebhooksDb({
28
32
  rootDir: constants.kRootDir,
29
33
  pruneDays: constants.kWebhookPruneDays,
@@ -3,34 +3,11 @@ const startServerLifecycle = ({
3
3
  PORT,
4
4
  isOnboarded,
5
5
  runOnboardedBootSequence,
6
- ensureManagedExecDefaults,
7
- ensureUsageTrackerPluginConfig,
8
- doSyncPromptFiles,
9
- reloadEnv,
10
- syncChannelConfig,
11
- readEnvFile,
12
- ensureGatewayProxyConfig,
13
- resolveSetupUrl,
14
- startGateway,
15
- watchdog,
16
- gmailWatchService,
17
6
  }) => {
18
7
  server.listen(PORT, "0.0.0.0", () => {
19
8
  console.log(`[alphaclaw] Express listening on :${PORT}`);
20
9
  if (isOnboarded()) {
21
- runOnboardedBootSequence({
22
- ensureManagedExecDefaults,
23
- ensureUsageTrackerPluginConfig,
24
- doSyncPromptFiles,
25
- reloadEnv,
26
- syncChannelConfig,
27
- readEnvFile,
28
- ensureGatewayProxyConfig,
29
- resolveSetupUrl,
30
- startGateway,
31
- watchdog,
32
- gmailWatchService,
33
- });
10
+ runOnboardedBootSequence();
34
11
  } else {
35
12
  console.log("[alphaclaw] Awaiting onboarding via Setup UI");
36
13
  }